231 Commits

Author SHA1 Message Date
Kevin O'Connor
bee179eab4 docs: Update release notes for v0.6.0 release
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-31 14:04:39 -04:00
Kevin O'Connor
039bb9f10f docs: Add Patreon link to FAQ page
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-31 13:55:41 -04:00
Kevin O'Connor
b0b4bc8958 heater: Add some comments to the thermistor math
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-29 16:40:26 -04:00
Kevin O'Connor
a6553538e6 docs: Update G-Codes.md with recent output_pin changes
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-28 15:11:10 -04:00
Kevin O'Connor
019666a6f6 docs: Add a section on adding new host modules to Code_Overview.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-28 13:12:41 -04:00
Kevin O'Connor
7579b9671b stepstats: Add documentation clarification
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-23 10:51:11 -04:00
Kevin O'Connor
99f3c99238 display: Round temperature and position to nearest whole number
The "%d" formatting truncates a floating point number - use "%.0f" to
show a rounded number.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-19 15:17:53 -04:00
Kevin O'Connor
aba04bf3bd display: Remove unneeded semicolons from file
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-19 15:16:01 -04:00
Kevin O'Connor
f5c67baac2 docs: Recommend running PID_CALIBRATE in Config_checks.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-18 12:43:41 -04:00
Kevin O'Connor
973ef97143 pid_calibrate: Move PID calibration logic from heater.py to new file
Drop support for M303 and PID_TUNE, and replace it with a new
PID_CALIBRATE command.  Move the logic for this command from heater.py
to a new pid_calibrate.py file in the extras/ directory.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-18 12:02:30 -04:00
Kevin O'Connor
310cdf88cc config: Remove tabs from printer-wanhao-duplicator-i3-v2.1-2017.cfg
Remove tabs from example config file and replace with spaces.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-18 11:44:56 -04:00
Kevin O'Connor
5c05a24947 docs: Fix typo in M220 description in G-Codes.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-18 11:44:56 -04:00
Kevin O'Connor
c93dad8eba config: Update generic-cramps.cfg with P9_23 machine enable
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-18 10:33:40 -04:00
Kevin O'Connor
fb3065cfa7 config: Change pullup_resistor to 2000 in generic-cramps.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 14:18:49 -04:00
Kevin O'Connor
e3f9ff6701 probe: Add z_offset parameter
Move the probe_z_offset parameter from delta_calibrate and
bed_tilt_calibrate to a z_offset parameter within the probe config
section.  It's easier to understand the z offset setting when it is in
the probe config section.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 14:07:15 -04:00
Kevin O'Connor
c95cc3fb66 bed_tilt: Take into account the XY position used with z_virtual_endstop
If a z_virtual_endstop is in use, then record the last XY position
that is used when the Z is homed.  Use that XY position to report what
change is needed to the z position_endstop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 13:47:31 -04:00
Kevin O'Connor
1dda4628a0 bed_tilt: Require at least 3 probing points for bed_tilt_calibrate
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 13:47:31 -04:00
Kevin O'Connor
36612fd544 probe: Default z_position to the configured minimum position on Z axis
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 13:47:31 -04:00
Kevin O'Connor
ef8c464d97 output_pin: Move pwm and digital output pins to new module in extras/
Rename the digital_output, pwm_output, and static_pwm_output config
sections to output_pin and move to a new module in the extras/
directory.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 13:46:36 -04:00
Kevin O'Connor
66eefa1da8 static_digital_output: Add static_digital_output section to extras/
Move the code for the static_digital_output config section from
chipmisc.py to a new file in the extras/ directory.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 13:06:51 -04:00
Kevin O'Connor
e78377eebd replicape: Add config controls for the servo[01]_enable lines
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-17 12:55:48 -04:00
Kevin O'Connor
451f7d5672 gcode: Position returned by M114 should be relative to last G92
It looks like OctoPrint is expecting the result from M114 to be
relative to the last G92 command.  Also, introduce GET_POSITION to
report the actual location that the printer is at.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-15 20:00:51 -04:00
Kevin O'Connor
ef820d98f6 docs: Add "will heaters be turned off on a crash" item to FAQ
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-13 11:36:45 -04:00
Kevin O'Connor
9dfe612516 verify_heater: Provide additional information on a heater error
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 23:31:17 -04:00
Kevin O'Connor
830cfc5414 klippy: Rework starting error message
Commit 9bc4239e now emphasizes the first line of a multi-line error
message, so rework startup_message to conform to that.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 23:15:22 -04:00
Kevin O'Connor
45afa04578 probe: Add some hints for common errors during PROBE
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 23:00:50 -04:00
Kevin O'Connor
9bc4239e9c gcode: On a multi-line error message, report the first line twice
Report the first line of a multi-line error message twice - once as
part of the informational content, and once with the "!!" error
prefix.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 22:56:31 -04:00
Kevin O'Connor
3a1cdc7d70 virtual_sdcard: Fix typo in seek error handling
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 22:36:39 -04:00
Kevin O'Connor
3a75748762 config: Reword the min_temp/max_temp description in example.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 14:37:02 -04:00
Kevin O'Connor
e336c24665 heater: Allow min_temp to go below zero
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-12 14:36:45 -04:00
Kevin O'Connor
849f4ed25f verify_heater: Scale hysteresis duration check
If a heater falls out of the target range, accumulate the temperature
differences to determine if an error should be raised.  This should
make it less likely to report an error for heaters that drift slightly
out of range, and it should make error reporting faster for heaters
that rapidly fall out of range.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-11 19:21:32 -04:00
Kevin O'Connor
57d342b455 verify_heater: Change default hysteresis to 10 degrees
A default of 4 degrees and 10 seconds may be too aggressive - change
the default to 10 degrees and 15 seconds.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-11 13:29:28 -04:00
Kevin O'Connor
5208fc38ed verify_heater: Add initial support for verifying heaters and sensors
Add runtime checks to heaters and temperature sensors to check for
possible hardware faults.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-11 00:49:11 -05:00
Kevin O'Connor
b549c3927e klippy: Allow external callers of try_load_module()
It can be useful to automatically pull in a module in the extras/
directory.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-11 00:04:30 -05:00
Kevin O'Connor
e53a589ac3 docs: Add SET_DUAL_CARRIAGE command to G-Codes.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-10 20:22:13 -05:00
Kevin O'Connor
4d48c111d8 cartesian: Initial support for dual carriages
Add support for additional carriages on cartesian printers.  This is
used by some printers to handle multiple extruders.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-09 10:26:21 -05:00
Kevin O'Connor
6c1e1dcc8d display: Use separate hd44780 screen drawing
Separate out the hd44780 screen drawing from the st7920 code.  Use a
layout that takes advantage of the 20 columns.  Add custom hd44780
fonts.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-08 22:44:40 -05:00
Kevin O'Connor
ddcf9a7ff7 docs: Add Tx command to G-Codes.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-08 14:40:26 -05:00
Kevin O'Connor
05ec7ca7ff docs: Add a G-Codes.md file with the list of available commands
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-08 14:11:20 -05:00
ghandye
ef0c80af51 config: Add working printer configuration for Wanhao Duplicator i3 v2.1
Working printer configuration for a Wanhao Duplicator i3 v2.1 and its
clones, including Monoprice Maker Select and Cocoon Create. Includes a
working config for the st7920-based front panel LCD.

Signed-off-by: Andy Ellsworth <andy+github@dar.net>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-08 10:26:21 -05:00
christian mock
d997821e58 config: Add LCD configuration to printer-trony-x5s-2017.cfg
I've added the LCD configuration for the Tronxy X5S printer and its
Melzi board and 128x64 LCD. This is tested and works.

I've also added the pins for the encoder, but that is untested.

Signed-off-by: christian mock <cm@tahina.priv.at>
2018-03-07 15:56:07 -05:00
Kevin O'Connor
385e6b67d9 config: Add display section to creality cr10 config
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-07 13:40:33 -05:00
Marcio Teixeira
04602b2470 display: Choose arrow character based on display
Choose the arrow character based on whether the display is ST7920 or
HD44780.

Signed-off-by: Marcio Teixeira <marcio@alephobjects.com>
2018-03-06 14:01:10 -05:00
cruwaller
923954772f homing: allow to disable homing retract
Homing retract can be disabled by setting homing_retract_dist to 0.

Signed-off-by: Petri Honkala <cruwaller@gmail.com>
2018-03-06 13:45:48 -05:00
Kevin O'Connor
5a16863465 config: Fix hd44780 typo in rambo and ramps example configs
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-06 13:15:09 -05:00
Kevin O'Connor
08a5f8a5ff display: Add initial support for LCD screens attached to an MCU
Add support for displaying basic status information on ST7920 and
HD44780 based LCDs that are attached directly to a micro-controller.

Signed-off-by: Marcio Teixeira <marcio@alephobjects.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-06 11:30:46 -05:00
Kevin O'Connor
43ac56766e serialqueue: Support sending messages at a background priority
Support low-priority message transmits.  This may be useful for bulk
commands that should be delayed util the comms are otherwise idle.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-06 11:30:41 -05:00
Kevin O'Connor
afc9bcf27b lcd_hd44780: Add micro-controller code for the HD44780 LCD chip
Add support for passing messages to an HD44780 chip via a 4-bit
bit-banging interface.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-06 11:30:34 -05:00
Kevin O'Connor
9140f36d99 lcd_st7920: Add micro-controller code for the ST7920 LCD chip
Add support for passing messages to an ST7920 chip via a SPI
bit-banging interface.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-06 11:30:30 -05:00
Kevin O'Connor
fa07be9346 mathutil: Move coordinate_descent() to new file
Add a new python file (mathutil.py) and move the coordinate_descent()
code to it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-04 09:56:50 -05:00
Kevin O'Connor
7290ed5f73 clocksync: Fix multi-mcu frequency adjustments with long moves
Commit 02ae2ab9 had a typo causing the new long move handling logic to
not take effect.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-03 21:54:25 -05:00
Kevin O'Connor
fc60bda4d1 virtual_sdcard: Remove trailing '*' checksum indicator from M23 command
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-02 11:58:00 -05:00
Kevin O'Connor
82a65e9f4a docs: Note the use of the virtual_sdcard feature
Note that virtual_sdcard helps on slower machines.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-03-02 11:28:01 -05:00
Kevin O'Connor
b139a8561f serialhdl: Add a wrapper around the results of lookup_command()
Add a lookup_command() method to the SerialReader class that provides
a wrapper that stores the serial and commandqueue references.  This
makes it easier to run the send() method.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-27 21:06:16 -05:00
Kevin O'Connor
8518da9824 config: Move description of phase adjusted endstops to config-extra.cfg
Move the description of stepper phase adjusted endstops to a new
section in config-extra.cfg.  This keeps the main example config file
a little more concise.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-27 09:34:25 -05:00
Kevin O'Connor
656cb2c417 build: Use git describe --always
Add --always flag to "git describe" command to get a build identifier
even if one checks out the repo with a depth parameter that prunes out
the last tagged version.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-26 21:50:05 -05:00
Kevin O'Connor
239c1ad5c9 serialqueue: Don't clear must_exit flag at start of pollreactor_run()
Avoid a timing race where must_exit is set before pollreactor_run() is
started.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-26 16:21:10 -05:00
Kevin O'Connor
7733e1d832 test: Add travis_fold markers to build output
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-26 15:21:39 -05:00
Kevin O'Connor
99b4122901 test: Add compile tests for at90usb1286 and atmega1284p chips
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-26 15:03:05 -05:00
Kevin O'Connor
28d70eaf0c test: Update travis build to include a basic klippy host test
Move the travis installation steps from the travis-build.sh script to
a new script (scripts/travis-install.sh).  Move the travis-build.sh
script to the scripts/ directory as well.

The data dictionaries built in the compile tests are useful during
host software testing, so run all the compile tests sequentially in a
single VM and save the data dictionaries after each build.  Also,
build all the config files found in the test/configs/ directory.

Create the python virtualenv environment during the install phase and
invoke the klippy.py host software in the build phase to perform a
basic host software sanity check.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-26 14:58:49 -05:00
Kevin O'Connor
5d635c5252 config: Use .0025 as the default z step_distance in sample configs
Don't use tiny step distances on the Z as that could cause a surprise
to users that copy the config without updating all the settings.  (A Z
step distance that is too small would cause a high motor velocity.)  A
step distance of .0025 is very popular in practice, so change the
default example configs to use that.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-25 16:00:55 -05:00
Kevin O'Connor
22d7e48aa2 config: Add an example config file that uses multiple MCUs
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-25 12:59:09 -05:00
Sebastian Meyer
a6b0649cb1 test: Add travis CI
Signed-off-by: Sebastian Meyer <ich@sebmeyer.de>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-25 10:54:59 -05:00
Sebastian Meyer
58dd6d9106 build: Link objects ahead of libraries to avoid failures on some platforms
Signed-off-by: Sebastian Meyer <ich@sebmeyer.de>
2018-02-25 10:39:06 -05:00
Douglas Hammond
d139389267 avr: Add at90usb646 support
Signed-off-by: Douglas Hammond wizhippo@gmail.com
2018-02-25 10:23:00 -05:00
Kevin O'Connor
e99c0f53f8 graphstats: Support selecting an mcu to graph from the command-line
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-19 23:12:14 -05:00
Kevin O'Connor
02ae2ab984 clocksync: Rework multi-mcu adjust to better handle long moves
The multi-mcu clock syncing code relies on the ability to periodically
update the mcu clock adjustments.  If a series of very long moves are
submitted then it is possible the adjustments could become unstable.
For example, if an adjustment is made to reduce a clock error over the
next couple of seconds, but it is applied to a longer period because
the next move lasts many seconds, then this would result in a bigger
adjustment for the following move, which would result in an even
bigger error when that move lasts many seconds.  This can repeat until
the system destabilizes.

Check for cases where the print_time is far in the future of the
current estimated print time and average over a longer period in that
case.  That should reduce the possibility of the adjustment code
becoming unstable.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-19 22:13:28 -05:00
Kevin O'Connor
97f7735c6a graphstats: Set size on frequency graph
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-18 20:42:28 -05:00
Kevin O'Connor
69486e45c1 ad5206: Warn if ad5206 enable pin is inverted
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-17 23:18:10 -05:00
Kevin O'Connor
5e8aaed41f docs: Add tips for porting to new kinematics to Code_Overview.md
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-17 13:48:53 -05:00
Kevin O'Connor
c128a9dfd5 probe: Increase calibration log level from debug to info
Output more information by default from the bed_tilt and
delta_calibrate commands.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-16 13:30:49 -05:00
Kevin O'Connor
6dc623b35d config: Expand homing_override description in sample-bltouch.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-13 15:29:49 -05:00
Kevin O'Connor
8fc3487a8a config: Reword description of set_position_x paramter in homing_override
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 15:35:10 -05:00
Kevin O'Connor
82db072151 config: Add a config snippet for the BLTouch probe
Add an example to help users with a BLTouch probe.  This is based on
information from @mediumo and @riddlez666.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 15:11:11 -05:00
Kevin O'Connor
41f73d0c8c config: Add a "printer-" prefix to example printer config files
Add a prefix to the example printer config files so that a sorted
directory listing shows all the samples bundled together.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 14:58:48 -05:00
Kevin O'Connor
08a1183a01 virtual_sdcard: Initial support for virtual sdcard
Add support for directly printing from a local file on the host.  This
may be useful if the host cpu is not fast enough to run OctoPrint
well.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 14:13:12 -05:00
Kevin O'Connor
f77e1b67f6 gcode: Introduce request_restart() method
Use request_restart() method instead of prep_restart() and
motor_heater_off().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 14:11:33 -05:00
Kevin O'Connor
11c7c110a1 gcode: Extract special cases from process_data()
Handle the unlikely case of pending commands in a new method
process_pending().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-11 14:10:09 -05:00
Dave Bacon
5abea041b4 mcu: Fix PWM static start value setup
config_setup uses ._start_value instead of ._static_value which isn't defined.

Signed-off-by: Dave Bacon <drb.github@mrbacon.com>
2018-02-10 13:00:22 -05:00
Dave Bacon
268d1cb27c docs: Update Config_checks.md
Navigation should be to the temperature tab to find the Tool temperature box.

Signed-off-by: Dave Bacon <drb.github@mrbacon.com>
2018-02-07 22:24:32 -05:00
Kevin O'Connor
561c84dd93 pins: Warn on invalid pin format
Raise an error if a pin description isn't formatted correctly.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-07 16:09:25 -05:00
Kevin O'Connor
955d940b60 bed_tilt: Negate parameters reported by bed_tilt_calibrate
The bed_tilt_calibrate command determines the tilt of the bed.
However, we need to report the tilt compensation which is the negative
of the bed tilt.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 14:52:38 -05:00
Kevin O'Connor
6ea36de9f2 config: Add a pointer to example-extras.cfg at the end of example.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 14:34:05 -05:00
Kevin O'Connor
e0cedfb853 heater: Report stats whenever the heater is active
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 13:55:59 -05:00
Kevin O'Connor
0a5b07f9da klippy: Allow any stats producer to determine when stats are needed
Instead of using the toolhead class to determine if stats should be
reported, allow every printer object with a stats() callback to
determine if stats are needed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 13:52:05 -05:00
Kevin O'Connor
08874b9c91 clocksync: Respond faster to clock changes
Average clock sync times over approximately 30 seconds instead of 120
seconds so that the clock prediction responds to changes faster.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 12:33:29 -05:00
Kevin O'Connor
8121a4a29f avrsim: Rework pacing mechanism
Rework the pacing mechanism to make it more stable.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-05 11:38:13 -05:00
Kevin O'Connor
d1c209c689 graphstats: Fix multi-mcu parsing with reordered stats
The mcu stats (that contain a prefix) may occur before some other
stats - make sure to only apply the stats prefix to those stats that
need it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-03 18:43:52 -05:00
Kevin O'Connor
f4bfce260a klippy: Introduce load_config_prefix() for modules that take parameters
Use both load_config() and load_config_prefix() functions when
dynamically loading a module from the extras directory - if the config
section name has parameters in it then use load_config_prefix().

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-03 12:53:11 -05:00
Kevin O'Connor
7e3adde542 klippy: No need to store fileconfig in main printer object
Just pass the fileconfig reference to the ConfigWrapper instances.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-03 12:17:42 -05:00
Kevin O'Connor
33bdc2fc32 util: Call set_nonblock() in create_pty()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-02 18:32:03 -05:00
Kevin O'Connor
0b76864453 gcode: Make sure need_ack is always restored on run_script()
Restore need_ack even on a G-Code exception.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-02 18:32:03 -05:00
Kevin O'Connor
56bfb3280a gcode: Keep reading input to check for M112 (emergency stop)
OctoPrint (and other software) will sometimes send additional commands
even if the previous command has not yet responded with an "ok".
Change the g-code input reading code to keep reading input so that an
M112 can be detected (and processed out-of-order).  To avoid the
extreme case where one writes an entire g-code file to the input
without any pacing, disable the input reading if more than 20 commands
are pending.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-02 11:00:46 -05:00
Kevin O'Connor
3ddbd34a7c gcode: Propagate errors from run_script()
If the script being run raises an error, then stop running that script
and return the error to the caller.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-01 12:18:47 -05:00
Kevin O'Connor
67f9c4948d gcode: Ack even empty lines
An empty input line should still return an "ok" message.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-02-01 11:03:49 -05:00
Kevin O'Connor
7d3600f918 docs: Add github issue template
Add an issue template requesting the klippy log file.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-31 14:30:05 -05:00
Kevin O'Connor
21df21b7af fan: Clarify hardware_pwm and allow cycle_time to be set on software pwm
Specify hardware pwm cycle times using the same method as software pwm
(in seconds, not clock ticks).  Allow the fan code to be configured
with an explicit cycle time even when using software pwm.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-29 13:08:15 -05:00
Kevin O'Connor
b7b216af7f avr: Round hardware pwm clock ticks to nearest divisor
Instead of rounding down to the nearest supported pwm divisor, round
to the nearest divisor.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-29 11:54:15 -05:00
Kevin O'Connor
077a56c2ca mcu: Default the restart method to 'command' on non-serial ports
If the mcu supports command restarts and it does not appear to use a
real serial port, then default the restart method to 'command'.  This
is a better default on boards with native USB support.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-29 10:25:25 -05:00
Kevin O'Connor
a67306c76b msgproto: Support default values in get_constant() calls
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-29 10:10:27 -05:00
Kevin O'Connor
6eefbe5e30 heater: Add stats reporting
Report the current temperature, current pwm setting, and target
temperature as statistics in the log.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 23:37:56 -05:00
Kevin O'Connor
650d55d7b2 docs: Remove bed level probe from todo
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 20:36:04 -05:00
Kevin O'Connor
03bcae8a98 config: Add SeeMeCNC Rostock Max V2 Delta Printer Config
As provided by @FEsmondeWhite

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 20:31:15 -05:00
Kevin O'Connor
1a67902858 homing_override: Allow moves prior to homing an axis
Add support for disabling homing checks via the homing_override
mechanism.  This may be useful to move an axis prior to homing it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
01bb4b291e probe: Create a probe:z_virtual_endstop pin
Create a virtual pin that may be used as the z endstop pin on
cartesian printers that use the probe in place of a z endstop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
e38c7df064 probe: Support activate/deactivate scripts on each probe
Allow a set of g-code scripts to be run on each probe invocation.
This may be useful for probes that need to be setup before they are
useful (eg, with servo actuated probes).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
3001a089c0 homing_override: Add basic support for running custom g-code on G28
Allow users to override the behavior of G28 using a new
"homing_override" config section.  This may be used on printers that
require specific steps during the homing process.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
39d62556b1 bed_tilt: Add support for automatic bed tilt move transformation
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
434341d074 delta_calibrate: Add initial support for a DELTA_CALIBRATE command
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
ce9db609ad probe: Initial support for Z-Probe hardware
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
6c252d30f5 delta: Allow the user to specify a minimum z position
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
978777f09f replicape: Move the replicape code from chipmisc.py to extras directory
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
5db84779c6 ad5206: Move the ad5206 code from chipmisc.py to extras directory
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
1e3a03fbee servo: Move the servo code from chipmisc.py to extras directory
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
01a89b951a multi_pin: Move the multi_pin code from chipmisc.py to extras directory
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
d166d1f692 fan: Move fan code to extras directory
The print cooling fan and printer heater_fan are independent modules
that can reside in the extras directory.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
9399911490 klippy: Add support for dynamic loading of python modules
Check if a config section matches a python filename in the
klippy/extras/ directory.  If it does, load that python code to handle
the config section.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
d3665699f1 klippy: Support generic printer_state() and stats() callbacks
Instead of hardcoding which objects are called on state transitions,
allow any "printer object" to be invoked if it has a printer_state()
method.  Convert connect, ready, shutdown, and disconnect callbacks to
this mechanism.

Similarly, allow all printer objects to provide a stats() callback.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
81013ba5c8 klippy: Add access methods and avoid peeking into the printer classes
Add get_reactor(), lookup_object(), lookup_module_objects(), and
set_rollover_info() to the main Printer class so that callers do not
need to peek into the class' members.  Similarly, add get_printer()
and get_name() methods to the ConfigWrapper class.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
f0a754e496 gcode: Add a run_script() helper method to run g-code scripts
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
51e1085dbc gcode: Extend register_command() so that it can also unregister a command
If register_command() is passed None as the function to call, then
treat it as an unregister command request.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 12:19:26 -05:00
Kevin O'Connor
47bb8b7cc2 graphstats: Fix for print_stall detection
The print_stall logic could cause large portions of the graph to show
up as 100% host utilized.  Rework the logic to avoid that.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 10:50:52 -05:00
Kevin O'Connor
33893ece1d logextract: Fix error in config file name print
Fix typo causing an error during the printing of the config file name.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-28 10:50:43 -05:00
Kevin O'Connor
1b3ef8a8fb config: Update anycubic-i3-mega-2017.cfg with stepstick fan
Based on conversation with Marcin Jedliński.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-22 11:28:20 -05:00
Kevin O'Connor
09eec3710d extruder: Add support for extruders sharing the same heater
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-21 23:25:11 -05:00
Kevin O'Connor
6fa95e12ea config: Update wanhao-duplicator-i3-plus-2017.cfg heater settings
Update config based on discussion with Max Holden and Josh Sutinen.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-21 18:52:14 -05:00
Kevin O'Connor
7a11b78fd4 config: Update anycubic-i3-mega-2017.cfg heater settings
Updates to heater settings.  Based on discussion with @bartolomeus and
Marcin Jedliński.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-21 18:47:34 -05:00
Kevin O'Connor
3d26bf6635 logextract: Better handle the case of a missing config section
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-21 15:56:13 -05:00
Kevin O'Connor
08444a8b89 docs: Try to make the klippy.log bug reporting more clear
Emphasize that the log file needs to be attached to the issue.
Encourage M112 to be issued on every bug report.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-21 13:08:31 -05:00
Kevin O'Connor
84c623e705 config: Add example generic-mini-rambo.cfg config file
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-17 01:00:20 -05:00
Douglas Hammond
72074078f9 config: Add tronxy x5s config
Signed-off-by: Douglas Hammond <wizhippo@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-16 22:20:55 -05:00
Kevin O'Connor
1d11c4e74d gcode: Use reset_last_position() from cmd_G28()
Use the existing reset_last_position() code in cmd_G28() instead of
manually implementing the logic.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-16 21:46:42 -05:00
Kevin O'Connor
f8bb383e9a extruder: Remove unused variable self.config
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-16 21:40:53 -05:00
Cymen Vig
8e5d228555 Add a CR-10S configuration file.
Signed-off-by: Cymen Vig <cymenvig@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-16 18:29:45 -05:00
Kevin O'Connor
6770aa96c9 graphstats: Use alpha transparency when plotting the stats
Use an alpha transparency on each of the stats, so that it is possible
to see how the values overlap.

Also, always enable the "awake time" stat - just use a lower alpha
transparency for it.

Also, explicitly set the graph size as some version of matplotlib have
a different default.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-14 10:46:48 -05:00
Kevin O'Connor
896c31fd05 graphstats: Fix filtering of normal buffer_time runoff stats
Update the mechanism to filter out cases where buffer_time is below
MAXBUFFER so that it works with the statistics currently generated.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-14 10:16:16 -05:00
Kevin O'Connor
054cbbe094 docs: Add a FAQ item on steps to upgrade the software
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-12 18:38:49 -05:00
Kevin O'Connor
0d8ddcadbb toolhead: Improve tracking of print_stall
The previous code increases print_stall if a move is proactively
flushed due to buffer_time_low and a follow up move is ready to send
before the existing moves are fully retired.  However, this would not
track the case where a move is proactively flushed and it takes an
extended amount of time to get sufficient moves to refill the queue.

Change the code so that print_stall increases anytime a new move is
queued after a proactive flush and there are existing moves not yet
fully retired.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-12 16:56:31 -05:00
Kevin O'Connor
d3eda337a9 logextract: Fix parsing of "is_shutdown" messages
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-12 16:44:21 -05:00
Kevin O'Connor
138f3c2646 config: Update avrsim.cfg to use unique pins
Commit 3833669c added checking that each pin is used once in the
config.  This exposed an error in the avrsim.cfg testing config -
analog1 conflicts with ar25.  Use analog7 to avoid this.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-12 16:44:21 -05:00
Kevin O'Connor
f1222565b8 docs: Update Debugging.md with information on logextract
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-11 13:48:40 -05:00
Kevin O'Connor
5caff594c5 heater: Report PID tuning results via the g-code terminal
Determine the median period from the pin tuning tests and use that to
recommend a set of PID parameters.  Report the results over the g-code
terminal.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 22:58:12 -05:00
Kevin O'Connor
5a68c636da chipmisc: Enable replicape steppers dynamically
Enable all the steppers via the shift registers when the first stepper
is enabled, and disable all the steppers once all steppers are
disabled.  This avoids having to enable all the steppers all of the
time.  Note that, this support is independent from the current control
for each stepper, which is still done individually.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 19:30:57 -05:00
Kevin O'Connor
02b141ac43 chipmisc: Fix replicape enable line setup
The enable lines are not one per stepper - instead there are 5
distinct enable settings.  Handle them properly.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 19:30:51 -05:00
Kevin O'Connor
47e458210e chipmisc: Support start values on pca9685 pins
Default the pca9685 enable line on if any of the pins attached to it
have a non-zero start value.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 19:30:47 -05:00
Kevin O'Connor
3833669c3a pins: Check if the same pin is referenced via different aliases
Change the update_command() call to use a new PinResolver class.  In
that new class, verify that the same pin isn't referenced in two
different parts of the config using different aliases for the pin.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 19:26:06 -05:00
Kevin O'Connor
df6528715e stepper: Automatically detect and handle shared stepper enable lines
It's common for multiple steppers to use the same stepper enable
line.  Detect this and only create a single digital_out oid on the
micro-controller.

Also, prohibit any other pin from being used multiple times in the
config file.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 19:10:26 -05:00
Kevin O'Connor
265b9097d5 pins: Rename parse_pin_desc() to lookup_pin()
Always set the pin_params['type'] field on a pin lookup.  Rename
parse_pin_desc() to lookup_pin() to make the change more clear.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-10 14:21:25 -05:00
mjedlinski
a6025686b6 config: Config file for Anycubic i3 Mega
Signed-off-by: Marcin Jedliński <mjedlinski@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 23:11:41 -05:00
Kevin O'Connor
ed80b92b59 mcu: Setup static values via setup_start_value() call
Add an "is_static" setting to setup_start_value() and remove the
setup_static() method.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 21:21:30 -05:00
nerobot
45e65580f7 config: Create creality-cr10
Signed-off-by: Steven Daglish <s.c.daglish@gmail.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 18:48:03 -05:00
Kevin O'Connor
f6f174ab36 extruder: Fix pressure advance on tiny moves with over extrusions
The extruder check_move() code will permit moves with huge extrusion
ratios if the amount extruded is tiny.  (Some slicers emit these silly
moves - they are allowed because they are harmless.)  Unfortunately,
the pressure advance code did not handle the huge extrusion ratios -
it would result in a massive, near instantaneous, extruder move in an
attempt to build pressure in the extruder.  Catch this case and limit
the impact to the pressure advance code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 14:39:19 -05:00
Kevin O'Connor
0cf06ee69a docs: Note pressure_advance over 0.20 is unlikely to improve quality
Update the pressure advance tuning document to note that 0.20 is
likely the ceiling of a useful pressure advance setting.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 11:07:26 -05:00
Kevin O'Connor
ed9dee4602 mcu: Only report an MCU timeout once
Don't flood the log with timeout messages.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-08 10:55:37 -05:00
tilsonm
f183871e28 config: Adopt Anet A8 cfg, Anet v1.0-1.5 Boards
This configuration file should work as a base for Anet A8 printers
using the stock Anet v1.0-1.5 boards from late 2016-2017, which are
based around the AVR atmega1284p. Minor tweaks may be required to such
things as /dev/serial/by-id/, thermistor sensor types, and offsets for
carriage and bed.

Signed-off-by: Matt Tilson <tilsonm09@gmail.com>
2018-01-07 22:22:08 -05:00
Kevin O'Connor
d891baa860 avr: Clear the TCNT1 register on timer startup
Just to be safe, clear the TCNT1 register during init.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-07 22:22:08 -05:00
Kevin O'Connor
f6cd51bfb7 avr: Clear the ADCSRA/ADCSRB registers on ADC init
When initializing the ADC, explicitly clear the registers (instead of
logically or'ing them with their previous values).

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-07 22:13:39 -05:00
Kevin O'Connor
83e9e92b9a docs: Add a table of contents to the FAQ
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-05 11:42:00 -05:00
Kevin O'Connor
9e4eb050f9 config: Note the need to flash a bootloader on the Melzi
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-05 10:23:58 -05:00
Kevin O'Connor
5a86391f78 avr: Only use the avrdude "wiring" protocol on atmega2560 devices
It appears the most common type of avrdude protocol for devices other
than the atmega2560 is the "arduino" protocol.  Update the build to
select a different protocol based on the avr processor type.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-05 10:02:15 -05:00
Kevin O'Connor
29c83cec22 docs: Update installation document with pointers to FAQ
Add some pointers to the FAQ for common pitfalls during installation.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-04 13:29:17 -05:00
Josh Sutinen
db927bd822 config: Added Wanhao Duplicator i3 Plus configuration
The Wanhao Duplicator i3 Plus (aka Maker Select Plus,
Microcenter Powerspec Plus, and others) uses an
atmega2560 AVR. The config is based on generic-ramps.cfg
with pin numbers updated from the official Wanhao Marlin
sources at https://github.com/garychen99/Duplicator-i3-plus

Signed-off-by: Josh Sutinen <josh@damagedgoodz.net>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-04 12:10:01 -05:00
Chris Lee
cb969527bc config: Update TAZ6 defaults for hotend
Use values from Lulzbot fork of Marlin.

Signed-off-by: Chris Lee <clee@mg8.org>
2018-01-04 10:58:47 -05:00
Kevin O'Connor
3ab9a8d26c gcode: Add support for M221 (set extrude factor) g-code command
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 17:12:31 -05:00
Kevin O'Connor
5db4886c9c gcode: Add support for M220 (set speed factor) g-code command
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 17:12:26 -05:00
Andy Silverman
b22a81cd34 heater: Added config for heater PWM cycle time.
Added the pwm_cycle_time option for heater config sections.  Defaults
to previous value of 0.100 seconds, but may be reduced to shorter times
at the expense of MCU workload and possible MOSFET heating, depending on
controller design.  Some printers that need bed PID to be enabled,
notably the Felixprinters series, are known to benefit from shorter
cycle times, as the default 10hz rate results in excessive voltage
supply droop.

While this option can be used on extruder heaters as well, there
is not expected to be any particular benefit from doing so unless
the extruder heater presents a particularly large load.

Signed-off-by: Andy Silverman <andrewsi@outlook.com>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 12:32:11 -05:00
Chris Lee
9fc5506c83 config: Add Lulzbot TAZ6 printer configuration
Signed-off-by: Chris Lee <clee@mg8.org>
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 12:10:40 -05:00
Kevin O'Connor
bba22ab7f0 heater: Provide symbolic names for pid check_busy (aka M109 / M190)
Add PID_SETTLE_DELTA and PID_SETTLE_SLOPE constants to the code to try
and make it a little more clear how the wait for temperature code
works.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 10:44:01 -05:00
Kevin O'Connor
a0b4cdb5c4 gcode: Don't wait for temperature if the temperature is disabled
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-03 10:33:10 -05:00
Kevin O'Connor
e4129a7e53 docs: Minor update to square.scad
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-02 21:56:29 -05:00
Kevin O'Connor
00ea3934ee logextract: Add support for extracting shutdown info on multiple mcus
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2018-01-02 21:48:25 -05:00
Kevin O'Connor
cf4c31cb88 graphstats: Add support for generating a graph of mcu frequency
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-30 18:29:58 -05:00
Kevin O'Connor
f10bd5726d graphstats: Use a smaller font in the graph legend
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-30 18:27:44 -05:00
Kevin O'Connor
7db6fa7bfc graphstats: Fix case where an Xwindows display may not be available
Matplotlib will try to open a display by default - tell it not to do
that so that it can run in batch mode on headless machines.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-30 18:26:49 -05:00
Kevin O'Connor
b05eb1e8e3 logextract: Fix handling of messages with retransmits
Properly handle the case where the original sequence number of a
message is not known.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-29 15:25:39 -05:00
Kevin O'Connor
1bdebeaebf docs: Add an initial configuration checklist document
Provide a document with basic steps that a user can run to verify that
the pins in the configuration file are set correctly.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-26 20:54:37 -05:00
Kevin O'Connor
479772ca00 docs: Add a CONTRIBUTING document
Add an initial document detailing best practices for contributing to
the document.  Add the "developer certificate of origin" document.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-24 12:33:01 -05:00
Kevin O'Connor
1d276d160f gcode: Dump internal g-code state in dump_debug()
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-21 22:27:49 -05:00
Kevin O'Connor
9313e58123 gcode: Group similar G-Code commands together
Group commands by category instead of listing them in numerical
order.  This is just code movement; no code changes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-21 20:58:25 -05:00
Kevin O'Connor
d778ae1846 build: Add gcc and binutils version to mcu data dictionary
Store the gcc and binutils versions used in the compilation of the
firmware in the firmware data dictionary.  Forward that information to
the log so it is available during debugging.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-21 20:32:10 -05:00
Kevin O'Connor
522093ef00 docs: Offer alternatives to ssh/scp/sftp
Try to make the Installation document more friendly to users not
familiar with the Linux command-line.  Offer some hints on how to
obtain ssh and how to edit the config file remotely.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-21 17:34:35 -05:00
Kevin O'Connor
d303e556ad sched: Interrupts must be disabled during setjmp() call
On the AVR platform (and possibly others) the longjmp() call will
restore the interrupt state saved during the setjmp() call.  So, the
setjmp() call must be invoked with interrupts disabled to ensure that
shutdown handling is run with interrupts disabled.  This fixes
potential corruption of the shutdown processing on AVR.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-21 12:54:33 -05:00
Kevin O'Connor
1d21bf66c6 homing: Handle speed rounding when homing speed greater than max_velocity
Commit 002dc0df added rounding to the homing speed, but it did not
work if the configured homing speed was less than the printer's
maximum velocity.  Move the speed rounding from stepper.py to
homing.py and make sure the rounded speed is less than the maximum
speed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-20 14:41:20 -05:00
Kevin O'Connor
1b07505973 chipmisc: Add multi_pin capability
Add the ability to alias multiple output pins from a single pin alias.
This makes it possible to support some cases where a single logical
output is driven by multiple output pins.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-18 21:00:06 -05:00
Kevin O'Connor
3c5649219f chipmisc: Add support for output pins set at runtime
Add the ability to define output pins that may be set at runtime with
a new SET_PIN extended g-code command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-18 20:59:07 -05:00
Kevin O'Connor
68d03e4a3e mcu: Allow both shutdown and startup values to be set for output pins
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-18 20:45:00 -05:00
Kevin O'Connor
e6e0a21b06 toolhead: Allow junction_deviation to be set to zero
Allow lookahead to be effectively disabled by setting
junction_deviation to zero.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-18 20:44:59 -05:00
Kevin O'Connor
0e0780a460 logextract: Update file comment
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-18 12:14:10 -05:00
Kevin O'Connor
9a4425c85a docs: Update square test print to contain notches on inside of square
Add additional notches and move them to the inside of the square where
they will not impact the outside perimeter test.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-17 12:18:25 -05:00
Kevin O'Connor
57c27f75ae gpiocmds: Clear SPF_NEXT_* flags on new schedule_soft_pwm_out
Properly handle the (unlikely) case that a schedule_soft_pwm_out
command is received before a previous command is fully processed,

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-16 20:21:38 -05:00
Kevin O'Connor
8d62318c5f clocksync: The clock estimate should add the rtt time not subtract it
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-16 20:21:38 -05:00
Kevin O'Connor
add528532e logextract: Rename extractconfig.py to logextract.py and add shutdown parsing
Add initial support for extracting out shutdown information from a
klippy.log file.  The shutdown dump will be reordered into the
sequence that they occurred, and timestamps/sequence numbers in the
mcu message dump will be expanded.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-16 20:21:34 -05:00
Kevin O'Connor
8944e2104d docs: Add FAQ item on AVR watchdog restart failures
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-11 18:17:14 -05:00
Kevin O'Connor
319221ee23 docs: Provide additional information on "make flash" failures
Add some additional information on "make flash" problems.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-11 17:33:29 -05:00
Kevin O'Connor
4a5801bb2e pins: Fix atmega168/328 mappings
The atmega168 and atmega328 need to define PE0 and PE1 in order to
support the 2 extra analog pins.

Also, support the arduino mappings for the atmega328.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-09 11:48:26 -05:00
Kevin O'Connor
f8acf0f54f delta: Default stepper_b/c position_endstop to stepper_a's
If the position_endstop is not set for stepper_b or stepper_c then use
the value from stepper_a.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-08 18:20:04 -05:00
Kevin O'Connor
bc5d900e61 delta: Support different arm lengths for each tower
Change the config file so that the delta arm length is specified
per-tower.  This makes it possible to support advanced calibration.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-08 18:20:04 -05:00
Kevin O'Connor
efb4a5daa1 delta: Rework actuator_to_cartesian() using trilateration
Use the formulas for trilateration (instead of the circumcenter
formulas) when calculating the position of the nozzle from the
position of the carriages.  The trilateration formula is more general
and it allows each tower to have a different arm length.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-08 18:09:19 -05:00
Kevin O'Connor
e0c947e188 mcu: Store MCU_stepper commanded_pos as a float
It's valid to consider the stepper at a position that is not on a step
interval - only the "mcu position" needs to be an integer.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-07 18:14:50 -05:00
Kevin O'Connor
3ffab763c0 stepper: Only align the stepper motor to a full step when requested
Add a new config option 'homing_endstop_align_zero' to enable the
alignment of the endstop to a stepper full step.  It's possible one
may wish to specify a homing_endstop_phase while not aligning the
endstop.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-07 17:35:19 -05:00
Kevin O'Connor
ef09ac5a7f extractconfig: Add helper script that extracts config files from a log
Add a helper script that can read a klippy.log file and extract the
printer.cfg files from it.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 21:23:24 -05:00
Kevin O'Connor
f6d4284d5c homing: Directly interact with the kinematic class on query_endstops()
Move the query_endstop logic out of toolhead.py and into homing.py.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:54 -05:00
Kevin O'Connor
8d9ca6f2dd homing: Directly interact with the kinematic class when homing
Move the homing logic out of toolhead.py and into homing.py.  This
simplifies the toolhead logic and centralizes the homing code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:54 -05:00
Kevin O'Connor
1d6af72de5 mcu: Remove unneeded MCU_digital_out.get_last_setting() method
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:54 -05:00
Kevin O'Connor
2a8dd5c51f mcu: Reset the stepper step clock on init - not after each motor on
Reset the last step clock during the init phase and after each home -
this simplifies the runtime code.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:54 -05:00
Kevin O'Connor
c78f66b8e8 mcu: Be sure all moves are completed before raising a home timeout
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:54 -05:00
Kevin O'Connor
b340fdcc4a homing: Make sure to clean up homing state even if homing fails
Make sure to always call MCU_endstop.home_wait() if
MCU_endstop.home_start() is invoked.  Rename
MCU_stepper.note_homing_triggered() to note_homing_end() and make sure
it is always called if MCU_stepper.note_homing_start() is invoked.

With these changes, MCU_endstop.home_finalize() is no longer needed.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 19:13:53 -05:00
Kevin O'Connor
7785d3a87d homing: Pass list of endstops (not steppers) to the homing code
The homing code wants the list of endstops to enable during a homing
operation - it's confusing to pass the steppers.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:49:14 -05:00
Kevin O'Connor
31db4cc772 mcu: Make sure steppers added to an endstop are on the same mcu
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
7932de11a7 mcu: Make sure all endstop objects are disabled on a reconnect
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
bc9cbc8133 gcode: Move SET_SERVO command from gcode.py to chipmisc.py
Now that commands can be registered dynamically, move the code for
SET_SERVO from gcode.py to the PrinterServo() class in chipmisc.py.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
b5a41d0dd1 gcode: Rework gcode handler setup to allow dynamic command registration
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
64a091fb98 gcode: Add a get_str() method
Add a get_str() method that can extract a required parameter from a
g-code command.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
80f23441dd gcode: Simplify exception handling
Translate caught exceptions into a gcode.error() exception.  This way
there is one standard place to invoke respond_error().  Also, always
reset the last_position on a handled error.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:51 -05:00
Kevin O'Connor
3a2d16abb3 gcode: Always allow M112 command
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-06 18:35:49 -05:00
Kevin O'Connor
c70cc8fadb klippy: Always recommend a FIRMWARE_RESTART on a shutdown event
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-03 19:52:25 -05:00
Kevin O'Connor
10e11950ae stepper: get_homed_offset() should return a float
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-12-03 19:48:47 -05:00
Kevin O'Connor
37788c1e55 docs: Add a FAQ document
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-30 15:19:40 -05:00
Kevin O'Connor
6d6638826c stepcompress: Fix proactive queue flushing on move with 64K+ steps
Commit e05c6354 changed the internal step compress queue from 64bit
integers to 32bit integers.  However, that commit broke the proactive
flushing of moves that could produce more than 64K steps.  This could
lead to large memory allocations and cpu slow downs on printers that
had a very large Z axis - possibly leading to a "Timer too close" mcu
shutdown.  Correct the code so that it properly generates a 64bit
flush clock.

Also, be sure to only expand / memmove the queue when there is no room
for a new element at the end.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-29 20:24:50 -05:00
Kevin O'Connor
6930a7de8d homing: Base homing cpu delay on estimated number of steps needed
Instead of adding 250ms to each homing operation add a time relative
to the number of estimated steps that are to be generated.  This
scales the delay to really large axes without adding a delay for
normal users.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-29 19:55:25 -05:00
Kevin O'Connor
6bbb84326d docs: Add further details on how to report a bug
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-21 18:27:22 -05:00
Kevin O'Connor
8c2fa2e2d6 stepper: Support for multiple steppers controlling a single axis
Allow multiple steppers to be defined for a single cartesian axis.
This adds support for dual-z setups.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:37:04 -05:00
Kevin O'Connor
38643f52c9 stepper: Add get_endstops() / set_position wrappers
Add wrappers around mcu_endstop and mcu_stepper so that the kinematic
classes do not need to directly access these low-level classes.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:36:45 -05:00
Kevin O'Connor
eecf3b6ea8 stepper: Store pointers to step_const and step_delta in PrinterStepper
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:29:23 -05:00
Kevin O'Connor
fc1d690d75 stepper: Remove unused variables from PrinterStepper class
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:29:23 -05:00
Kevin O'Connor
d10380e73f stepper: Additional code comments
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:29:23 -05:00
Kevin O'Connor
aaeda540b6 stepper: Calculate the stepper name directly from the config section
There is no need to pass the name to the PrinterStepper class as it
can determine the name itself.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-18 17:29:22 -05:00
Kevin O'Connor
fda988889b heater: Avoid math errors on extreme ADC readings
Avoid log(0) and divide by zero errors in the thermistor calc_temp()
method.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-13 11:07:20 -05:00
Kevin O'Connor
b58a897b70 config: List E1 micro-step pins in order in generic-rambo.cfg
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-11-08 11:14:37 -05:00
Kevin O'Connor
84d8cf9b7e config: Add a generic-printrboard.cfg file
Add an example config file for the Printrboard board.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-10-30 10:56:08 -04:00
Kevin O'Connor
8463a83324 graphstats: Fix graphing script
Update the graphstats.py graphing script so that it works with recent
stats changes - the stats can now contain groups that end with a ':'
and the print_time is no longer reset to zero on a new print.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
2017-10-29 20:36:33 -04:00
110 changed files with 7291 additions and 1801 deletions

13
.travis.yml Normal file
View File

@@ -0,0 +1,13 @@
# This is a travis-ci.org continuous integration configuration file.
language: c
addons:
apt:
packages:
- gcc-avr
- avr-libc
- wget
install: ./scripts/travis-install.sh
script: ./scripts/travis-build.sh

View File

@@ -80,12 +80,12 @@ $(OUT)%.o.ctr: $(OUT)%.o
$(OUT)compile_time_request.o: $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) ./scripts/buildcommands.py
@echo " Building $@"
$(Q)cat $(patsubst %.c, $(OUT)src/%.o.ctr,$(src-y)) > $(OUT)klipper.compile_time_request
$(Q)$(PYTHON) ./scripts/buildcommands.py -d $(OUT)klipper.dict $(OUT)klipper.compile_time_request $(OUT)compile_time_request.c
$(Q)$(PYTHON) ./scripts/buildcommands.py -d $(OUT)klipper.dict -t "$(CC);$(AS);$(LD);$(OBJCOPY);$(OBJDUMP);$(STRIP)" $(OUT)klipper.compile_time_request $(OUT)compile_time_request.c
$(Q)$(CC) $(CFLAGS) -c $(OUT)compile_time_request.c -o $@
$(OUT)klipper.elf: $(patsubst %.c, $(OUT)src/%.o,$(src-y)) $(OUT)compile_time_request.o
@echo " Linking $@"
$(Q)$(CC) $(CFLAGS_klipper.elf) $^ -o $@
$(Q)$(CC) $^ $(CFLAGS_klipper.elf) -o $@
################ Kconfig rules

View File

@@ -46,7 +46,7 @@ nozzle_diameter: 0.500
filament_diameter: 3.500
heater_pin: ar4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog1
sensor_pin: analog7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08

View File

@@ -35,7 +35,7 @@ homing_speed: 50
step_pin: ar46
dir_pin: ar48
enable_pin: !ar62
step_distance: .01
step_distance: .0025
endstop_pin: ^ar18
position_endstop: 0.5
position_max: 200

View File

@@ -16,12 +16,22 @@ dir_pin: ar55
enable_pin: !ar38
step_distance: .01
endstop_pin: ^ar2
homing_speed: 50
position_endstop: 297.05
# Distance (in mm) between the nozzle and the bed when the nozzle is
# in the center of the build area and the endstop triggers. This
# parameter must be provided for stepper_a; for stepper_b and
# stepper_c this parameter defaults to the value specified for
# stepper_a.
arm_length: 333.0
# Length (in mm) of the diagonal rod that connects this tower to the
# print head. This parameter must be provided for stepper_a; for
# stepper_b and stepper_c this parameter defaults to the value
# specified for stepper_a.
#angle:
# This option specifies the angle (in degrees) that the tower is
# at. The default is 210 for stepper_a, 330 for stepper_b, and 90
# for stepper_c.
homing_speed: 50
# The stepper_b section describes the stepper controlling the front
# right tower (at 330 degrees).
@@ -31,7 +41,6 @@ dir_pin: ar61
enable_pin: !ar56
step_distance: .01
endstop_pin: ^ar15
position_endstop: 297.05
# The stepper_c section describes the stepper controlling the rear
# tower (at 90 degrees).
@@ -41,7 +50,6 @@ dir_pin: ar48
enable_pin: !ar62
step_distance: .01
endstop_pin: ^ar19
position_endstop: 297.05
[extruder]
step_pin: ar26
@@ -91,11 +99,33 @@ max_z_velocity: 150
# maximum speed of up/down moves (which require a higher step rate
# 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. This parameter must be provided.
#minimum_z_position: 0
# The minimum Z position that the user may command the head to move
# to. The default is 0.
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.
# The delta_calibrate section enables a DELTA_CALIBRATE extended
# g-code command that can calibrate the tower endstop positions and
# angles.
[delta_calibrate]
radius: 50
# Radius (in mm) of the area that may be probed. This is typically
# the size of the printer bed. This parameter must be provided.
#speed: 50
# The speed (in mm/s) of non-probing moves during the
# calibration. The default is 50.
#horizontal_move_z: 5
# The height (in mm) that the head should be commanded to move to
# just prior to starting a probe operation. The default is 5.
#manual_probe:
# If true, then DELTA_CALIBRATE will perform manual probing. If
# false, then a PROBE command will be run at each probe
# point. Manual probing is accomplished by manually jogging the Z
# position of the print head at each probe point and then issuing a
# NEXT extended g-code command to record the position at that
# point. The default is false if a [probe] config section is present
# and true otherwise.

View File

@@ -2,6 +2,67 @@
# additional devices that may be configured on a printer. The snippets
# in this file may be copied into the main printer.cfg file. See the
# "example.cfg" file for description of common config parameters.
#
# Note, where an extra config section creates additional pins, the
# section defining the pins must be listed in the config file before
# any sections using those pins.
# Z height probe. One may define this section to enable Z height
# probing hardware. When this section is enabled, PROBE and
# QUERY_PROBE extended g-code commands become available. The probe
# section also creates a virtual probe:z_virtual_endstop pin. One may
# set the stepper_z endstop_pin to this virtual pin on cartesian style
# printers that use the probe in place of a z endstop.
#[probe]
#pin: ar15
# Probe detection pin. This parameter must be provided.
#z_offset:
# The distance (in mm) between the bed and the nozzle when the probe
# triggers. This parameter must be provided.
#speed: 5.0
# Speed (in mm/s) of the Z axis when probing. The default is 5mm/s.
#activate_gcode:
# A list of G-Code commands (one per line) to execute prior to each
# probe attempt. This may be useful if the probe needs to be
# activated in some way. The default is to not run any special
# G-Code commands on activation.
#deactivate_gcode:
# A list of G-Code commands (one per line) to execute after each
# probe attempt completes. The default is to not run any special
# G-Code commands on deactivation.
# Bed tilt compensation. One may define a [bed_tilt] config section to
# enable move transformations that account for a tilted bed.
#[bed_tilt]
#x_adjust: 0
# The amount to add to each move's Z height for each mm on the X
# axis. The default is 0.
#y_adjust: 0
# The amount to add to each move's Z height for each mm on the Y
# axis. The default is 0.
# The remaining parameters control a BED_TILT_CALIBRATE extended
# g-code command that may be used to calibrate appropriate x and y
# adjustment parameters.
#points:
# A newline separated list of X,Y points that should be probed
# during a BED_TILT_CALIBRATE command. The default is to not enable
# the command.
#speed: 50
# The speed (in mm/s) of non-probing moves during the
# calibration. The default is 50.
#horizontal_move_z: 5
# The height (in mm) that the head should be commanded to move to
# just prior to starting a probe operation. The default is 5.
#manual_probe:
# If true, then BED_TILT_CALIBRATE will perform manual probing. If
# false, then a PROBE command will be run at each probe
# point. Manual probing is accomplished by manually jogging the Z
# position of the print head at each probe point and then issuing a
# NEXT extended g-code command to record the position at that
# point. The default is false if a [probe] config section is present
# and true otherwise.
# In a multi-extruder printer add an additional extruder section for
@@ -13,6 +74,13 @@
#step_pin: ar36
#dir_pin: ar34
#...
#shared_heater:
# If this extruder uses the same heater already defined for another
# extruder then place the name of that extruder here. For example,
# should extruder3 and extruder4 share a heater then the extruder3
# config section should define the heater and the extruder4 section
# should specify "shared_heater: extruder3". The default is to not
# reuse an existing heater.
#deactivate_gcode:
# A list of G-Code commands (one per line) to execute on a G-Code
# tool change command (eg, "T1") that deactivates this extruder and
@@ -27,6 +95,102 @@
# activation.
# Support for cartesian printers with dual carriages on a single
# axis. The active carriage is set via the SET_DUAL_CARRIAGE extended
# g-code command. The "SET_DUAL_CARRIAGE CARRIAGE=1" command will
# activate the carriage defined in this section (CARRIAGE=0 will
# return activation to the primary carriage). Dual carriage support is
# typically combined with extra extruders - use the SET_DUAL_CARRIAGE
# command in the activate_gcode / deactivate_gcode section of the
# appropriate extruder. Be sure to also use that mechanism to park the
# carriages during deactivation.
#[dual_carriage]
#axis:
# The axis this extra carriage is on (either x or y). This parameter
# must be provided.
#step_pin:
#dir_pin:
#enable_pin:
#step_distance:
#endstop_pin:
#position_endstop:
#position_min:
#position_max:
# See the example.cfg for the definition of the above parameters.
# Heater and temperature sensor verification. Heater verification is
# automatically enabled for each heater that is configured on the
# printer. Use verify_heater sections to change the default settings.
#[verify_heater heater_config_name]
#heating_gain: 2
# The minimum temperature (in Celsius) that the heater must increase
# by when approaching a new target temperature. The default is 2.
#check_gain_time:
# The amount of time (in seconds) that the heating_gain must be met
# in before an error is raised. The default is 20 seconds for
# extruders and 60 seconds for heater_bed.
#hysteresis: 5
# The difference between the target temperature and the current
# temperature for the heater to be considered within range of the
# target temperature. The default is 5.
#max_error: 120
# The maximum temperature difference a heater that falls outside the
# target temperature range may accumulate before an error is
# raised. For example, if the target temperature is 200, the
# hysteresis is 5, the max_error is 120, and the temperature is
# reported at 185 degrees for 12 seconds then an error would be
# raised (or 24 seconds at 190, or 120 seconds at 194, etc.). The
# default is 120.
# Multi-stepper axes. On a cartesian style printer, the stepper
# controlling a given axis may have additional config blocks defining
# steppers that should be stepped in concert with the primary
# stepper. One may define any number of sections with a numeric suffix
# starting at 1 (for example, "stepper_z1", "stepper_z2", etc.).
#[stepper_z1]
#step_pin: ar36
#dir_pin: ar34
#enable_pin: !ar30
#step_distance: .005
# See the example.cfg for the definition of the above parameters.
#endstop_pin: ^ar19
# If an endstop_pin is defined for the additional stepper then the
# stepper will home until the endstop is triggered. Otherwise, the
# endstop will home until the endstop on the primary stepper for the
# axis is triggered.
# Stepper phase adjusted endstops. The following additional parameters
# may be added to a stepper axis definition to improve the accuracy of
# endstop switches.
#[stepper_z]
#homing_stepper_phases:
# One may set this to the number of phases of the stepper motor
# driver (which is the number of micro-steps multiplied by
# four). This parameter must be provided if using stepper phase
# adjustments.
#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
# then set this to 0.200 for 200um). The default is
# homing_stepper_phases*step_distance.
#homing_endstop_phase:
# This specifies the phase of the stepper motor driver to expect
# when hitting the endstop. Only set this value if one is sure the
# stepper motor driver is reset every time the mcu is reset. If this
# is not set, then the stepper phase will be detected on the first
# home and that phase will be used on all subsequent homes.
#homing_endstop_align_zero: False
# If true then the code will arrange for the zero position on the
# axis to occur at a full step on the stepper motor. (If used on the
# Z axis and the print layer height is a multiple of a full step
# distance then every layer will occur on a full step.) The default
# is False.
# Heater cooling fans (one may define any number of sections with a
# "heater_fan" prefix). A "heater fan" is a fan that will be enabled
# whenever its associated heater is active. In the event of an MCU
@@ -79,7 +243,8 @@
# Statically configured digital output pins (one may define any number
# of sections with a "static_digital_output" prefix). Pins configured
# here will be setup as a GPIO output during MCU configuration.
# here will be setup as a GPIO output during MCU configuration. They
# can not be changed at run-time.
#[static_digital_output my_output_pins]
#pins:
# A comma separated list of pins to be set as GPIO output pins. The
@@ -87,35 +252,60 @@
# with "!". This parameter must be provided.
# Statically configured PWM output pins (one may define any number of
# sections with a "static_pwm_output" prefix). Pins configured here
# will be setup as PWM outputs during MCU configuration.
#[static_pwm_output my_output_pwm]
# Run-time configurable output pins (one may define any number of
# sections with an "output_pin" prefix). Pins configured here will be
# setup as output pins and one may modify them at run-time using the
# "SET_PIN PIN=my_pin VALUE=.1" extended g-code command.
#[output_pin my_pin]
#pin:
# The pin to configure as PWM output. This parameter must be
# The pin to configure as an output. This parameter must be
# provided.
#pwm: False
# Set if the output pin should be capable of
# pulse-width-modulation. If this is true, the value fields should
# be between 0 and 1; if it is false the value fields should be
# either 0 or 1. The default is False.
#static_value:
# If this is set, then the pin is assigned to this value at startup
# and the pin can not be changed during runtime. A static pin uses
# slightly less ram in the micro-controller. The default is to use
# runtime configuration of pins.
#value:
# The value to statically set the PWM output to. This is typically
# set to a number between 0.0 and 1.0 with 1.0 being full on and 0.0
# being full off. However, the range may be changed with the 'scale'
# parameter (see below). This parameter must be provided.
#hard_pwm:
# 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 to force hardware PWM with the given cycle time in clock
# ticks. The default is to use software PWM.
# The value to initially set the pin to during MCU
# configuration. The default is 0 (for low voltage).
#shutdown_value:
# The value to set the pin to on an MCU shutdown event. The default
# is 0 (for low voltage).
#cycle_time: 0.100
# The amount of time (in seconds) per PWM cycle when using software
# based PWM. The default is 0.100 seconds.
# The amount of time (in seconds) per PWM cycle. It is recommended
# this be 10 milliseconds or greater when using software based
# PWM. The default is 0.100 seconds for pwm pins.
#hardware_pwm: False
# Enable this to use hardware PWM instead of software PWM. The
# default is False.
#scale:
# This parameter can be used to alter how the 'value' parameter is
# interpreted. If provided, then the 'value' parameter should be
# between 0.0 and 'scale'. This may be useful when configuring a PWM
# pin that controls a stepper voltage reference. The 'scale' can be
# set to the equivalent stepper amperage if the PWM were fully
# enabled, and then the 'value' parameter can be specified using the
# desired amperage for the stepper. The default is to not scale the
# 'value' parameter.
# This parameter can be used to alter how the 'value' and
# 'shutdown_value' parameters are interpreted for pwm pins. If
# provided, then the 'value' parameter should be between 0.0 and
# 'scale'. This may be useful when configuring a PWM pin that
# controls a stepper voltage reference. The 'scale' can be set to
# the equivalent stepper amperage if the PWM were fully enabled, and
# then the 'value' parameter can be specified using the desired
# amperage for the stepper. The default is to not scale the 'value'
# parameter.
# Multiple pin outputs (one may define any number of sections with a
# "multi_pin" prefix). A multi_pin output creates an internal pin
# alias that can modify multiple output pins each time the alias pin
# is set. For example, one could define a "[multi_pin my_fan]" object
# containing two pins and then set "pin=multi_pin:my_fan" in the
# "[fan]" section - on each fan change both output pins would be
# updated. These aliases may not be used with stepper motor pins.
#[multi_pin my_multi_pin]
#pins:
# A comma separated list of pins associated with this alias. This
# parameter must be provided.
# Statically configured AD5206 digipots connected via SPI bus (one may
@@ -148,6 +338,63 @@
# default is to not scale the 'channel_x' parameters.
# Homing override. One may use this mechanism to run a series of
# g-code commands in place of a G28 found in the normal g-code input.
# This may be useful on printers that require a specific procedure to
# home the machine.
#[homing_override]
#gcode:
# A list of G-Code commands (one per line) to execute in place of
# all G28 commands found in the normal g-code input. If a G28 is
# contained in this list of commands then it will invoke the normal
# homing procedure for the printer. The commands listed here must
# home all axes. This parameter must be provided.
#set_position_x:
#set_position_y:
#set_position_z:
# If specified, the printer will assume the axis is at the specified
# position prior to running the above g-code commands. Setting this
# disables homing checks for that axis. This may be useful if the
# head must move prior to invoking the normal G28 mechanism for an
# axis. The default is to not force a position for an axis.
# A virtual sdcard may be useful if the host machine is not fast
# enough to run OctoPrint well. It allows the Klipper host software to
# directly print gcode files stored in a directory on the host using
# standard sdcard G-Code commands (eg, M24).
#[virtual_sdcard]
#path: ~/.octoprint/uploads/
# The path of the local directory on the host machine to look for
# g-code files. This is a read-only directory (sdcard file writes
# are not supported). One may point this to OctoPrint's upload
# directory (generally ~/.octoprint/uploads/ ). This parameter must
# be provided.
# Support for a display attached to the micro-controller.
#[display]
#lcd_type:
# The type of LCD chip in use. This may be either "hd44780" (which
# is used in "RepRapDiscount 2004 Smart Controller" type displays)
# or "st7920" (which is used in "RepRapDiscount 12864 Full Graphic
# Smart Controller" type displays). This parameter must be
# provided.
#rs_pin:
#e_pin:
#d4_pin:
#d5_pin:
#d6_pin:
#d7_pin:
# The pins connected to an hd44780 type lcd. These parameters must
# be provided when using an hd44780 display.
#cs_pin:
#sclk_pin:
#sid_pin:
# The pins connected to an st7920 type lcd. These parameters must
# be provided when using an st7920 display.
# Replicape support - see the generic-replicape.cfg file for further
# details.
#[replicape]

View File

@@ -0,0 +1,87 @@
# This file contains an example configuration with three
# micro-controllers simultaneously controlling a single printer.
# See both the example.cfg and example-extras.cfg file for a
# description of available parameters.
# The main micro-controller is used as the timing source for all the
# micro-controllers on the printer. Typically, both the X and Y axes
# are connected to the main micro-controller.
[mcu]
serial: /dev/ttyACM0
pin_map: arduino
# The "zboard" micro-controller will be used to control the Z axis.
[mcu zboard]
serial: /dev/ttyACM1
pin_map: arduino
# The "auxboard" micro-controller will be used to control the heaters.
[mcu auxboard]
serial: /dev/ttyACM2
pin_map: arduino
[stepper_x]
step_pin: ar54
dir_pin: ar55
enable_pin: !ar38
step_distance: .0125
endstop_pin: ^ar3
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_y]
step_pin: ar60
dir_pin: !ar61
enable_pin: !ar56
step_distance: .0125
endstop_pin: ^ar14
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_z]
step_pin: zboard:ar46
dir_pin: zboard:ar48
enable_pin: !zboard:ar62
step_distance: .0025
endstop_pin: ^zboard:ar18
position_endstop: 0.5
position_max: 200
[extruder]
step_pin: auxboard:ar26
dir_pin: auxboard:ar28
enable_pin: !auxboard:ar24
step_distance: .002
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: auxboard:ar10
sensor_type: EPCOS 100K B57560G104F
sensor_pin: auxboard:analog13
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: auxboard:ar8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: auxboard:analog14
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: auxboard:ar9
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100

View File

@@ -57,26 +57,6 @@ position_max: 200
# direction (away from zero); if false, home towards zero. The
# default is true if position_endstop is near position_max and false
# if near position_min.
#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
# 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
# then set this to 0.200 for 200um). This setting is used with
# homing_stepper_phases and is only useful if that parameter is also
# configured.
#homing_endstop_phase: 0
# This specifies the phase of the stepper motor driver to expect
# when hitting the endstop. This setting is only meaningful if
# homing_stepper_phases is also set. Only set this value if one is
# sure the stepper motor driver is reset every time the mcu is
# reset. If this is not set, but homing_stepper_phases is set, then
# the stepper phase will be detected on the first home and that
# phase will be used on all subsequent homes.
# 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
@@ -197,15 +177,24 @@ pid_Kd: 114
#pid_integral_max:
# The maximum "windup" the integral term may accumulate. The default
# is to use the same value as max_power.
#pwm_cycle_time: 0.100
# Time in seconds for each software PWM cycle of the heater. It is
# not recommended to set this unless there is an electrical
# requirement to switch the heater faster than 10 times a second.
# The default is 0.100 seconds.
#min_extrude_temp: 170
# The minimum temperature (in Celsius) at which extruder move
# commands may be issued. The default is 170 Celsius.
min_temp: 0
# 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 parameter must be provided.
# The maximum range of valid temperatures (in Celsius) that the
# heater must remain within. This controls a safety feature
# implemented in the micro-controller code - should the measured
# temperature ever fall outside this range then the micro-controller
# will go into a shutdown state. This check can help detect some
# heater and sensor hardware failures. Set this range just wide
# enough so that reasonable temperatures do not result in an
# error. These parameters must be provided.
# The heater_bed section describes a heated bed (if present - omit
# section if not present).
@@ -234,12 +223,13 @@ pin: ar9
# 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 fan. The default is 1.0.
#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 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.
#cycle_time: 0.010
# The amount of time (in seconds) for each PWM power cycle to the
# fan. It is recommended this be 10 milliseconds or greater when
# using software based PWM. The default is 0.010 seconds.
#hardware_pwm: False
# Enable this to use hardware PWM instead of software PWM. The
# default is False.
#kick_start_time: 0.100
# Time (in seconds) to run the fan at full speed when first enabling
# it (helps get the fan spinning). The default is 0.100 seconds.
@@ -253,7 +243,7 @@ serial: /dev/ttyACM0
pin_map: arduino
# This option may be used to enable Arduino pin name aliases. The
# default is to not enable the aliases.
#restart_method: arduino
#restart_method:
# 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) is common on Arduino
@@ -262,7 +252,8 @@ pin_map: arduino
# 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'.
# 'arduino' if the micro-controller communicates over a serial port,
# 'command' otherwise.
# The printer section controls high level printer settings.
[printer]
@@ -298,3 +289,6 @@ max_z_accel: 30
# centripetal velocity cornering algorithm. A larger number will
# permit higher "cornering speeds" at the junction of two moves. The
# default is 0.02mm.
# Looking for more options? Check the example-extras.cfg file.

View File

@@ -34,7 +34,7 @@ homing_speed: 50
step_pin: P8_19
dir_pin: P8_18
enable_pin: !P9_14
step_distance: 0.00025
step_distance: .0025
endstop_pin: ^P9_13
position_endstop: 0
position_max: 200
@@ -48,6 +48,7 @@ nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: P9_15
sensor_type: EPCOS 100K B57560G104F
pullup_resistor: 2000
sensor_pin: P9_36
control: pid
pid_Kp: 22.2
@@ -59,6 +60,7 @@ max_temp: 250
[heater_bed]
heater_pin: P8_11
sensor_type: EPCOS 100K B57560G104F
pullup_resistor: 2000
sensor_pin: P9_33
control: watermark
min_temp: 0
@@ -77,3 +79,8 @@ max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[output_pin machine_enable]
pin: P9_23
value: 1
shutdown_value: 0

View File

@@ -2,9 +2,12 @@
# this config, the firmware should be compiled for the AVR
# atmega1284p.
# Note that the "make flash" command does not work with Melzi
# boards. The boards are typically flashed with this command:
# avrdude -p atmega1284p -c avrisp -P /dev/ttyUSB0 -U flash:w:out/klipper.elf.hex
# Note, a number of Melzi boards are shipped without a bootloader. In
# that case, an external programmer will be needed to flash a
# bootloader to the board (for example, see
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
# ). Once that is done, one should be able to use the standard "make
# flash" command to flash Klipper.
# See the example.cfg file for a description of available parameters.
@@ -32,7 +35,7 @@ homing_speed: 50
step_pin: PB3
dir_pin: !PB2
enable_pin: !PA5
step_distance: 0.00025
step_distance: .0025
endstop_pin: ^!PC4
position_endstop: 0.5
position_max: 200

View File

@@ -0,0 +1,109 @@
# This file contains common pin mappings for Mini-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: .005
endstop_pin: ^PB6
#endstop_pin: ^PC7
position_endstop: 0
position_max: 250
[stepper_y]
step_pin: PC1
dir_pin: !PL0
enable_pin: !PA6
step_distance: .005
endstop_pin: ^PB5
#endstop_pin: ^PA2
position_endstop: 0
position_max: 210
[stepper_z]
step_pin: PC2
dir_pin: PL2
enable_pin: !PA5
step_distance: .0025
endstop_pin: ^PB4
#endstop_pin: ^PA1
position_endstop: 0.5
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: PE5
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: PG5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF2
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PH5
#[heater_fan nozzle_cooling_fan]
#pin: PH3
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[output_pin stepper_xy_current]
pin: PL3
pwm: True
scale: 2.0
cycle_time: .002
hardware_pwm: True
static_value: 1.3
[output_pin stepper_z_current]
pin: PL4
pwm: True
scale: 2.0
cycle_time: .002
hardware_pwm: True
static_value: 1.3
[output_pin stepper_e_current]
pin: PL5
pwm: True
scale: 2.0
cycle_time: .002
hardware_pwm: True
static_value: 1.25
[static_digital_output stepper_config]
pins:
PG1, PG0,
PK7, PG2,
PK6, PK5,
PK3, PK4
[static_digital_output yellow_led]
pins: !PB7

View File

@@ -0,0 +1,76 @@
# This file contains common pin mappings for Printrboard boards (rev B
# through D). To use this config the firmware should be compiled for
# the AVR at90usb1286.
# Note that the "make flash" command is unlikely to work on the
# Printrboard. See the RepRap Printrboard wiki page for instructions
# on flashing.
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PA0
dir_pin: !PA1
enable_pin: !PE7
step_distance: .0125
endstop_pin: ^PE3
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_y]
step_pin: PA2
dir_pin: PA3
enable_pin: !PE6
step_distance: .0125
endstop_pin: ^PB0
position_endstop: 0
position_max: 200
homing_speed: 50
[stepper_z]
step_pin: PA4
dir_pin: !PA5
enable_pin: !PC7
step_distance: .0025
endstop_pin: ^PE4
position_endstop: 0.5
position_max: 200
[extruder]
step_pin: PA6
dir_pin: PA7
enable_pin: !PC3
step_distance: .002
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PC5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF1
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PC4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF0
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PC6
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100

View File

@@ -29,7 +29,7 @@ homing_speed: 50
step_pin: PC2
dir_pin: PL2
enable_pin: !PA5
step_distance: 0.00025
step_distance: .0025
endstop_pin: ^PB4
#endstop_pin: ^PC7
position_endstop: 0.5
@@ -103,7 +103,24 @@ pins:
PK7, PG2,
PK6, PK5,
PK3, PK4,
PK2, PK1
PK1, PK2
[static_digital_output yellow_led]
pins: !PB7
# "RepRapDiscount 2004 Smart Controller" type displays
#[display]
#lcd_type: hd44780
#rs_pin: PG4
#e_pin: PG3
#d4_pin: PJ2
#d5_pin: PJ3
#d6_pin: PJ7
#d7_pin: PJ4
# "RepRapDiscount 128x64 Full Graphic Smart Controller" type displays
#[display]
#lcd_type: st7920
#cs_pin: PG4
#sclk_pin: PJ2
#sid_pin: PG3

View File

@@ -30,7 +30,7 @@ homing_speed: 50
step_pin: ar46
dir_pin: ar48
enable_pin: !ar62
step_distance: 0.00025
step_distance: .0025
endstop_pin: ^ar18
#endstop_pin: ^ar19
position_endstop: 0.5
@@ -82,3 +82,20 @@ max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
# "RepRapDiscount 2004 Smart Controller" type displays
#[display]
#lcd_type: hd44780
#rs_pin: ar16
#e_pin: ar17
#d4_pin: ar23
#d5_pin: ar25
#d6_pin: ar27
#d7_pin: ar29
# "RepRapDiscount 128x64 Full Graphic Smart Controller" type displays
#[display]
#lcd_type: st7920
#cs_pin: ar16
#sclk_pin: ar23
#sid_pin: ar17

View File

@@ -13,6 +13,62 @@
# See the example.cfg file for a description of available parameters.
[mcu]
serial: /dev/rpmsg_pru30
pin_map: beaglebone
[mcu host]
serial: /tmp/klipper_host_mcu
# The "replicape" config section adds "replicape:stepper_x_enable"
# virtual stepper enable pins (for steppers x, y, z, e, and h) and
# "replicape:power_x" PWM output pins (for hotbed, e, h, fan0, fan1,
# fan2, and fan3) that may then be used elsewhere in the config file.
[replicape]
revision: B3
# The replicape hardware revision. Currently only revision "B3" is
# supported. This parameter must be provided.
#enable_pin: !P9_41
# The replicape global enable pin. The default is !P9_41.
host_mcu: host
# The name of the mcu config section that communicates with the
# Klipper "linux process" mcu instance. This parameter must be
# provided.
#standstill_power_down: False
# This parameter controls the CFG6_ENN line on all stepper
# motors. True sets the enable lines to "open". The default is
# False.
#servo0_enable: False
# This parameter controls whether end_stop_X_2 is used for endstops
# (via P9_11) or for servo_0 (via P9_14). The default is False.
#servo1_enable: False
# This parameter controls whether end_stop_Y_2 is used for endstops
# (via P9_28) or for servo_1 (via P9_16). The default is False.
stepper_x_microstep_mode: spread16
# This parameter controls the CFG1 and CFG2 pins of the given
# stepper motor driver. Available options are: disable, 1, 2,
# spread2, 4, 16, spread4, spread16, stealth4, and stealth16. The
# default is disable.
stepper_x_current: 0.5
# The configured maximum current (in Amps) of the stepper motor
# driver. This parameter must be provided if the stepper is not in a
# disable mode.
#stepper_x_chopper_off_time_high: False
# This parameter controls the CFG0 pin of the stepper motor driver
# (True sets CFG0 high, False sets it low). The default is False.
#stepper_x_chopper_hysteresis_high: False
# This parameter controls the CFG4 pin of the stepper motor driver
# (True sets CFG4 high, False sets it low). The default is False.
#stepper_x_chopper_blank_time_high: True
# This parameter controls the CFG5 pin of the stepper motor driver
# (True sets CFG5 high, False sets it low). The default is True.
stepper_y_microstep_mode: spread16
stepper_y_current: 0.5
stepper_z_microstep_mode: spread16
stepper_z_current: 0.5
stepper_e_microstep_mode: 16
stepper_e_current: 0.5
[stepper_x]
step_pin: P8_17
dir_pin: P8_26
@@ -37,11 +93,18 @@ homing_speed: 50
step_pin: P8_13
dir_pin: P8_14
enable_pin: replicape:stepper_z_enable
step_distance: 0.00025
step_distance: .0025
endstop_pin: ^P9_13
position_endstop: 0
position_max: 200
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 25
max_z_accel: 30
[extruder]
step_pin: P9_12
dir_pin: P8_15
@@ -69,56 +132,3 @@ max_temp: 130
[fan]
pin: replicape:power_fan0
[mcu]
serial: /dev/rpmsg_pru30
pin_map: beaglebone
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 25
max_z_accel: 30
[mcu host]
serial: /tmp/klipper_host_mcu
# The "replicape" config section adds "replicape:stepper_x_enable"
# virtual stepper enable pins (for steppers x, y, z, e, and h) and
# "replicape:power_x" PWM output pins (for hotbed, e, h, fan0, fan1,
# fan2, and fan3) that may then be used elsewhere in the config file.
[replicape]
revision: B3
# The replicape hardware revision. Currently only revision "B3" is
# supported. This parameter must be provided.
#enable_pin: !P9_41
# The replicape global enable pin. The default is !P9_41.
host_mcu: host
# The name of the mcu config section that communicates with the
# Klipper "linux process" mcu instance. This parameter must be
# provided.
stepper_x_microstep_mode: spread16
# This parameter controls the CFG1 and CFG2 pins of the given
# stepper motor driver. Available options are: disable, 1, 2,
# spread2, 4, 16, spread4, spread16, stealth4, and stealth16. The
# default is disable.
stepper_x_current: 0.5
# The configured maximum current (in Amps) of the stepper motor
# driver. This parameter must be provided if the stepper is not in a
# disable mode.
#stepper_x_chopper_off_time_high: False
# This parameter controls the CFG0 pin of the stepper motor driver
# (True sets CFG0 high, False sets it low). The default is False.
#stepper_x_chopper_hysteresis_high: False
# This parameter controls the CFG4 pin of the stepper motor driver
# (True sets CFG4 high, False sets it low). The default is False.
#stepper_x_chopper_blank_time_high: True
# This parameter controls the CFG5 pin of the stepper motor driver
# (True sets CFG5 high, False sets it low). The default is True.
stepper_y_microstep_mode: spread16
stepper_y_current: 0.5
stepper_z_microstep_mode: spread16
stepper_z_current: 0.5
stepper_e_microstep_mode: 16
stepper_e_current: 0.5

View File

@@ -0,0 +1,79 @@
# This file contains common pin mappings for Anet A8 printer from 2016
# and 2017. To use this config, the firmware should be compiled for
# the AVR atmega1284p.
# Note that the "make flash" command does not work with Anet boards -
# the boards are typically flashed with this command:
# avrdude -p atmega1284p -c arduino -b 57600 -P /dev/ttyUSB0 -U out/klipper.elf.hex
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PD7
dir_pin: PC5
enable_pin: !PD6
step_distance: .01
endstop_pin: ^!PC2
position_endstop: -30
position_max: 220
position_min: -30
homing_speed: 50
[stepper_y]
step_pin: PC6
dir_pin: PC7
enable_pin: !PD6
step_distance: .01
endstop_pin: ^!PC3
position_endstop: -8
position_min: -8
position_max: 220
homing_speed: 50
[stepper_z]
step_pin: PB3
dir_pin: !PB2
enable_pin: !PA5
step_distance: .0025
endstop_pin: ^!PC4
position_endstop: 0.5
position_max: 240
homing_speed: 20
[extruder]
step_pin: PB1
dir_pin: PB0
enable_pin: !PD6
step_distance: .0105
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PD5
sensor_type: ATC Semitec 104GT-2
sensor_pin: PA7
control: pid
pid_Kp: 2.151492
pid_Ki: 0.633897
pid_Kd: 230.042965
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PD4
sensor_type: ATC Semitec 104GT-2
sensor_pin: PA6
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PB4
[mcu]
serial: /dev/ttyUSB0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 1000
max_z_velocity: 20
max_z_accel: 100

View File

@@ -0,0 +1,93 @@
# This file contains pin mappings for the Anycubic i3 Mega with
# Ultrabase from 2017. (This config may work on an Anycubic i3 Mega v1
# prior to the Ultrabase if you comment out the definition of the
# endstop_pin in the stepper_z1 section.) 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: ar54
dir_pin: !ar55
enable_pin: !ar38
step_distance: .0125
endstop_pin: ^!ar3
position_min: -5
position_endstop: -5
position_max: 210
homing_speed: 30.0
[stepper_y]
step_pin: ar60
dir_pin: ar61
enable_pin: !ar56
step_distance: .0125
endstop_pin: ^!ar42
position_endstop: 0
position_max: 210
homing_speed: 30.0
[stepper_z]
step_pin: ar46
dir_pin: ar48
enable_pin: !ar62
step_distance: .0025
endstop_pin: ^!ar18
position_endstop: 0.0
position_max: 205
homing_speed: 5.0
[stepper_z1]
step_pin: ar36
dir_pin: ar34
enable_pin: !ar30
step_distance: .0025
endstop_pin: ^!ar43
[extruder]
step_pin: ar26
dir_pin: ar28
enable_pin: !ar24
step_distance: .010799
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: ar10
sensor_type: ATC Semitec 104GT-2
sensor_pin: analog13
control: pid
pid_Kp: 15.717
pid_Ki: 0.569
pid_Kd: 108.451
min_temp: 0
max_temp: 245
[heater_fan extruder_fan]
pin: ar44
[heater_bed]
heater_pin: ar8
sensor_type: EPCOS 100K B57560G104F
sensor_pin: analog14
control: pid
pid_Kp: 74.883
pid_Ki: 1.809
pid_Kd: 775.038
min_temp: 0
max_temp: 110
[fan]
pin: ar9
[mcu]
serial: /dev/ttyUSB0
pin_map: arduino
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 10
max_z_accel: 60
[heater_fan stepstick_fan]
pin: ar7

View File

@@ -0,0 +1,88 @@
# This file contains common pin mappings for the 2017 Creality
# CR-10. To use this config, the firmware should be compiled for the
# AVR atmega1284p.
# Note, a number of Melzi boards are shipped without a bootloader. In
# that case, an external programmer will be needed to flash a
# bootloader to the board (for example, see
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
# ). Once that is done, one should be able to use the standard "make
# flash" command to flash Klipper.
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PD7
dir_pin: !PC5
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^PC2
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_y]
step_pin: PC6
dir_pin: !PC7
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^PC3
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_z]
step_pin: PB3
dir_pin: PB2
enable_pin: !PA5
step_distance: .0025
endstop_pin: ^PC4
position_endstop: 0.0
position_max: 400
[extruder]
step_pin: PB1
dir_pin: !PB0
enable_pin: !PD6
step_distance: 0.010526
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PD5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA7
control: pid
pid_Kp: 22.57
pid_Ki: 1.72
pid_Kd: 73.96
min_temp: 0
max_temp: 250
[heater_bed]
heater_pin: PD4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA6
control: pid
pid_Kp: 426.68
pid_Ki: 78.92
pid_Kd: 576.71
min_temp: 0
max_temp: 130
[fan]
pin: PB4
[mcu]
serial: /dev/ttyUSB0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100
[display]
lcd_type: st7920
cs_pin: PA3
sclk_pin: PA1
sid_pin: PC1

View File

@@ -0,0 +1,75 @@
# This file contains pin mappings for the 2017 Creality CR-10S. 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: ar54
dir_pin: ar55
enable_pin: !ar38
step_distance: .0125
endstop_pin: ^ar3
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_y]
step_pin: ar60
dir_pin: ar61
enable_pin: !ar56
step_distance: .0125
endstop_pin: ^ar14
position_endstop: 0
position_max: 300
homing_speed: 50
[stepper_z]
step_pin: ar46
dir_pin: !ar48
enable_pin: !ar62
step_distance: .0025
endstop_pin: ^ar18
position_endstop: 0.5
position_max: 200
[extruder]
step_pin: ar26
dir_pin: ar28
enable_pin: !ar24
step_distance: .010526
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: pid
pid_Kp: 690.34
pid_Ki: 111.47
pid_Kd: 1068.83
min_temp: 0
max_temp: 130
[fan]
pin: ar9
[mcu]
serial: /dev/ttyUSB0
pin_map: arduino
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 5
max_z_accel: 100

View File

@@ -0,0 +1,116 @@
# This file contains pin mappings for the Lulzbot TAZ 6 circa 2017. 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: .010000
endstop_pin: ^PB6
position_endstop: -20
position_min: -20
position_max: 300
homing_speed: 50
[stepper_y]
step_pin: PC1
dir_pin: !PL0
enable_pin: !PA6
step_distance: .010000
endstop_pin: ^PA1
position_endstop: 306
position_min: -20
position_max: 306
homing_speed: 50
[stepper_z]
step_pin: PC2
dir_pin: PL2
enable_pin: !PA5
step_distance: 0.000625
endstop_pin: ^!PB4
position_endstop: -0.7
position_min: -1.5
position_max: 270
homing_speed: 1
[extruder]
step_pin: PC3
dir_pin: !PL6
enable_pin: !PA4
step_distance: 0.001182
nozzle_diameter: 0.400
filament_diameter: 2.920
heater_pin: PH6
sensor_type: ATC Semitec 104GT-2
sensor_pin: PF0
control: pid
pid_Kp: 28.79
pid_Ki: 1.91
pid_Kd: 108.51
min_temp: 0
max_temp: 300
min_extrude_temp: 140
#[extruder1]
#step_pin: PC4
#dir_pin: PL7
#enable_pin: !PA3
#heater_pin: PH4
#sensor_pin: PF1
#...
[heater_bed]
heater_pin: PE5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF2
control: watermark
min_temp: 0
max_temp: 130
[fan]
pin: PH5
[heater_fan nozzle_cooling_fan]
pin: PH3
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 3000
max_z_velocity: 2
max_z_accel: 10
[ad5206 stepper_digipot]
enable_pin: PD7
scale: 2.08
# Channel 1 is E0, 2 is E1, 3 is unused, 4 is Z, 5 is X, 6 is Y
channel_1: 1.34
channel_2: 1.0
channel_4: 1.1
channel_5: 1.1
channel_6: 1.1
# Enable 16 micro-steps on steppers X, Y, Z, E0, E1
[static_digital_output stepper_config]
pins:
PG1, PG0,
PK7, PG2,
PK6, PK5,
PK3, PK4,
PK1, PK2
[static_digital_output yellow_led]
pins: !PB7
[display]
lcd_type: st7920
cs_pin: PG4
sclk_pin: PJ2
sid_pin: PG3

View File

@@ -72,7 +72,8 @@ pin: PH5
[heater_fan nozzle_fan]
pin: PH3
max_power: 0.61
hard_pwm: 1
cycle_time: .000030
hardware_pwm: True
[mcu]
serial: /dev/ttyACM0

View File

@@ -0,0 +1,94 @@
# This file constains the pin mappings for the SeeMeCNC Rostock Max
# (version 2) delta printer from 2015. 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_a]
step_pin: PC0
dir_pin: !PL1
enable_pin: !PA7
step_distance: .0125
endstop_pin: ^PA2
homing_speed: 50
position_endstop: 380
arm_length: 290.800
[stepper_b]
step_pin: PC1
dir_pin: PL0
enable_pin: !PA6
step_distance: .0125
endstop_pin: ^PA1
[stepper_c]
step_pin: PC2
dir_pin: !PL2
enable_pin: !PA5
step_distance: .0125
endstop_pin: ^PC7
[extruder]
step_pin: PC3
dir_pin: !PL6
enable_pin: !PA4
step_distance: .010793
nozzle_diameter: 0.500
filament_diameter: 1.750
heater_pin: PH6
sensor_type: ATC Semitec 104GT-2
sensor_pin: PF0
control: pid
pid_Kp: 20.9700
pid_Ki: 1.3400
pid_Kd: 80.5600
min_temp: 0
max_temp: 300
[heater_bed]
heater_pin: PE5
sensor_type: ATC Semitec 104GT-2
sensor_pin: PF2
control: pid
pid_Kp: 46.510
pid_Ki: 1.040
pid_Kd: 500.000
min_temp: 0
max_temp: 300
[fan]
pin: PH5
[heater_fan nozzle_cooling_fan]
pin: PH4
heater: extruder
[mcu]
serial: /dev/ttyACM0
[printer]
kinematics: delta
max_velocity: 300
max_accel: 3000
max_z_velocity: 150
delta_radius: 174.75
[ad5206 stepper_digipot]
enable_pin: PD7
scale: 2.08
channel_1: 1.34
channel_2: 1.0
channel_4: 1.1
channel_5: 1.1
channel_6: 1.1
[static_digital_output stepper_config]
pins:
PG1, PG0,
PK7, PG2,
PK6, PK5,
PK3, PK4,
PK1, PK2
[static_digital_output yellow_led]
pins: !PB7

View File

@@ -0,0 +1,89 @@
# This file contains pin mappings for the Tronxy X5S (circa 2017). To
# use this config, the firmware should be compiled for the AVR
# atmega1284p.
# Note, a number of Melzi boards are shipped without a bootloader. In
# that case, an external programmer will be needed to flash a
# bootloader to the board (for example, see
# http://www.instructables.com/id/Flashing-a-Bootloader-to-the-CR-10/
# ). Once that is done, one should be able to use the standard "make
# flash" command to flash Klipper.
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PD7
dir_pin: !PC5
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^!PC2
position_endstop: 0
position_max: 330
homing_speed: 50
[stepper_y]
step_pin: PC6
dir_pin: !PC7
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^!PC3
position_endstop: 0
position_max: 310
homing_speed: 50
[stepper_z]
step_pin: PB3
dir_pin: PB2
enable_pin: !PD6
step_distance: .0025
endstop_pin: ^!PC4
position_endstop: 0.5
position_max: 400
[extruder]
step_pin: PB1
dir_pin: PB0
enable_pin: !PD6
step_distance: .0111
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PD5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA7
control: pid
pid_Kp: 22.2
pid_Ki: 1.08
pid_Kd: 114
min_temp: 0
max_temp: 275
[heater_bed]
heater_pin: PD4
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PA6
control: watermark
min_temp: 0
max_temp: 150
[fan]
pin: PB4
[mcu]
serial: /dev/ttyUSB0
[printer]
kinematics: corexy
max_velocity: 300
max_accel: 1000
max_z_velocity: 20
max_z_accel: 100
[display]
lcd_type: st7920
cs_pin: PA1
sclk_pin: PC0
sid_pin: PA3
# buttons are:
# PD2, PD3: encoder
# PA5: click

View File

@@ -0,0 +1,78 @@
# This file contains pin mappings for the Wanhao Duplicator i3 Plus
# (circa 2017). To use this config, the firmware should be compiled
# for the AVR atmega2560.
# Pin numbers and other parameters were extracted from the
# official Marlin source available at:
# https://github.com/garychen99/Duplicator-i3-plus
# See the example.cfg file for a description of available parameters.
[stepper_x]
step_pin: PF7
dir_pin: !PK0
enable_pin: !PF6
step_distance: .0125
endstop_pin: ^!PF0
position_endstop: 0
position_max: 200
homing_speed: 30.0
[stepper_y]
step_pin: PK2
dir_pin: !PK3
enable_pin: !PK1
step_distance: .0125
endstop_pin: ^!PA2
position_endstop: 0
position_max: 200
homing_speed: 30.0
[stepper_z]
step_pin: PK5
dir_pin: PK7
enable_pin: !PK4
step_distance: .0025
endstop_pin: ^!PA1
position_endstop: 0.5
position_max: 180
[extruder]
step_pin: PF4
dir_pin: PF5
enable_pin: !PF3
step_distance: 0.010417
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PG5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PF1
control: pid
pid_Kp: 30.850721
pid_Ki: .208175
pid_Kd: 192.298728
min_temp: 0
max_temp: 260
[heater_bed]
heater_pin: PE5
sensor_type: EPCOS 100K B57560G104F
sensor_pin: PK6
control: pid
pid_Kp: 64.095903
pid_Ki: 1.649830
pid_Kd: 622.531455
min_temp: 0
max_temp: 110
[fan]
pin: PE3
[mcu]
serial: /dev/ttyUSB0
[printer]
kinematics: cartesian
max_velocity: 300
max_accel: 800
max_z_velocity: 5
max_z_accel: 100

View File

@@ -0,0 +1,159 @@
# This file contains pin mappings and other appropriate default parameters
# for a Wanhao Duplicator i3 v2.1 and its clones
# (Monoprice Maker Select, Cocoon Create, etc.)
# See the files example.cfg and example-extras.cfg for a description of available parameters.
#
# This will probably work on older revisions (v1.0, v2.0) of the printer
# but is untested on those versions.
#
# For best results with klipper and the Wanhao Duplicator i3, follow these
# guidelines:
#
# - Flash a bootloader to the Melzi board in the printer
# See http://www.instructables.com/id/Using-an-Arduino-to-Flash-the-Melzi-Board-Wanhao-I/
#
# - Make sure the auto-reset jumper is *enabled* on the Melzi board
# (See step 1 in the bootloader tutorial above)
#
# - Locate the USB serial port for your printer in /dev/serial/by-id/ format.
# See https://github.com/KevinOConnor/klipper/blob/master/docs/FAQ.md#wheres-my-serial-port
# It will be something like:
# /dev/serial/by-id/usb-FTDI_FT232R_USB_UART_ABCD1234-if00-port0
#
# - Configure klipper to compile firmware for the AVR atmega1284p
#
# - At this point, "make flash FLASH_DEVICE=..." should successfully
# flash your printer board. Use the /dev/serial/by-id/ format for
# FLASH_DEVICE to ensure consistent results.
# See https://github.com/KevinOConnor/klipper/blob/master/docs/FAQ.md#the-make-flash-command-doesnt-work
# if you have problems.
#
# - Copy this sample file you are currently reading to ~/printer.cfg,
# and customize the following parameters:
# * [extruder] > step_distance
#
# This is the inverse of "E steps" (extruder steps per mm) from the stock
# Wanhao Repetier-based firmware.
# (See https://3dprinterwiki.info/extruder-steps/ )
#
# For example, if your E-steps are set to 107.0 steps per mm,
# then step_distance should be (1 / 107.0) ~= .009346
#
# * [extruder] > PID parameters (pid_Kp, pid_Ki, pid_Kd)
# * [heater_bed] > PID parameters (pid_Kp, pid_Ki, pid_Kd)
#
# PID values from stock Wanhao firmware (Repetier) do not
# translate directly to klipper. You will need to run klipper's
# PID autotune function for the extruder and bed. After getting the
# klipper firmware up and running, run the PID_CALIBRATE procedures
# by sending these commands via octoprint terminal (one per autotune):
#
# extruder: PID_CALIBRATE HEATER=extruder TARGET=<temp>
# heated bed: PID_CALIBRATE HEATER=heater_bed TARGET=<temp>
#
# After the autotune process completes, PID parameter results
# can be found in the Octoprint terminal tab (if you're quick)
# or in /tmp/klippy.log.
#
# Enter the PID parameters into the appropriate sections of ~/printer.cfg .
#
# * [extruder] > max_temp
# * [heater_bed] > max_temp
#
# The max temps included in this printer config are limited to 230 for extruder
# and 70 for heated bed. If your printer has been modified to handle higher temps
# (like an upgraded hot end or a separate MOSFET for your heated bed), you may
# want to increase these values.
#
# * [mcu] > serial
#
# Enter the USB serial port of the printer in /dev/serial/by-id/ format
# for best results.
#
# - Power cycle the Wanhao Duplicator i3
#
# - Issue the command "RESTART" via the Octoprint terminal tab (similar to
# how you would send a manual gcode command, but send the word RESTART).
# This tells klipper to reload its config file and do an internal reset.
# You should then see a status screen appear on the printer's LCD.
#
# - Be sure to follow these instructions before attempting any prints:
# https://github.com/KevinOConnor/klipper/blob/master/docs/Config_checks.md
[stepper_x]
step_pin: PD7
dir_pin: PC5
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^!PC2
position_endstop: 0
position_max: 200
homing_speed: 40
[stepper_y]
step_pin: PC6
dir_pin: PC7
enable_pin: !PD6
step_distance: .0125
endstop_pin: ^!PC3
position_endstop: 0
position_max: 200
homing_speed: 40
[stepper_z]
step_pin: PB3
dir_pin: !PB2
enable_pin: !PA5
step_distance: 0.0025
endstop_pin: ^!PC4
position_endstop: 0.5
position_max: 180
homing_speed: 2
[extruder]
step_pin: PB1
dir_pin: !PB0
enable_pin: !PD6
step_distance: .009346
nozzle_diameter: 0.400
filament_diameter: 1.750
heater_pin: PD5
sensor_type: NTC 100K beta 3950
sensor_pin: PA7
control: pid
pid_Kp: 18.214030
pid_Ki: 0.616380
pid_Kd: 134.556146
min_temp: 0
max_temp: 230
[heater_bed]
heater_pin: PD4
sensor_type: NTC 100K beta 3950
sensor_pin: PA6
control: pid
pid_Kp: 71.321
pid_Ki: 1.989
pid_Kd: 639.210
min_temp: 0
max_temp: 70
[fan]
pin: PB4
[mcu]
serial: /dev/ttyUSB0
restart_method: command
[printer]
kinematics: cartesian
max_velocity: 200
max_accel: 1000
max_z_velocity: 2
max_z_accel: 100
[display]
lcd_type: st7920
cs_pin: PC1
sclk_pin: PD3
sid_pin: PC0

56
config/sample-bltouch.cfg Normal file
View File

@@ -0,0 +1,56 @@
# This file provides example config file settings for the BLTouch
# automatic bed leveling sensor. This file is just a "snippet" of
# sections specific to the BLTouch - it must be added to a config file
# containing the configuration of the rest of the printer.
# Be sure to review and update this config with the appropriate pins
# and coordinates for your printer.
# See the "example.cfg" and "example-extras.cfg" files for a
# description of config parameters.
# Define the BLTouch servo
[servo bltouch]
pin: ar32
maximum_servo_angle: 180
minimum_pulse_width: 0.0006
maximum_pulse_width: 0.0024
# Define a probe using the BLTouch
[probe]
pin: ar30
activate_gcode:
SET_SERVO SERVO=bltouch ANGLE=10
SET_SERVO SERVO=bltouch ANGLE=60
G4 P200
deactivate_gcode:
SET_SERVO SERVO=bltouch ANGLE=90
# Example bed_tilt config section
[bed_tilt]
#x_adjust:
#y_adjust:
points:
100,100
10,10
10,100
10,190
100,10
100,190
190,10
190,100
190,190
probe_z_offset: 2.345
# If the BLTouch is used to home the Z axis, then define a
# homing_override section, use probe:z_virtual_endstop as the
# endstop_pin in the stepper_z section, and set the endstop_position
# in the stepper_z section to match the probe's probe_z_offset.
#[homing_override]
#set_position_z: 5
#gcode:
# ; G90 ; Uncomment these 2 lines to blindly lift the Z 2mm at start
# ; G1 Z7 F600
# G28 X0 Y0
# G1 X100 Y100 F3600
# G28 Z0

38
docs/CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,38 @@
# Contributing to Klipper
Thank you for contributing to Klipper! Please take a moment to read
this document.
## Creating a new issue
Please see the [contact page](Contact.md) for information on creating
an issue. In particular, **we need the klippy.log file** attached to
bug reports. Also, be sure to read the [FAQ](FAQ.md) to see if a
similar issue has already been raised.
## Submitting a pull request
Contributions of Code and documentation are managed through github
pull requests. Each commit should have a commit message formatted
similar to the following:
```
module: Capitalized, short (50 chars or less) summary
More detailed explanatory text, if necessary. Wrap it to about 75
characters or so. In some contexts, the first line is treated as the
subject of an email and the rest of the text as the body. The blank
line separating the summary from the body is critical (unless you omit
the body entirely); tools like rebase can get confused if you run the
two together.
Further paragraphs come after blank lines..
Signed-off-by: My Name <myemail@example.org>
```
It is important to have a "Signed-off-by" line on each commit - it
certifies that you agree to the
[developer certificate of origin](developer-certificate-of-origin). It
must contain your real name (sorry, no pseudonyms or anonymous
contributions) and contain a current email address.

View File

@@ -29,6 +29,8 @@ files.
The **scripts/** directory contains build-time scripts useful for
compiling the micro-controller code.
The **test/** directory contains automated test cases.
During compilation, the build may create an **out/** directory. This
contains temporary build time objects. The final micro-controller
object that is built is **out/klipper.elf.hex** on AVR and
@@ -77,7 +79,7 @@ interrupts disabled.
Much of the functionality of the micro-controller involves working
with General-Purpose Input/Output pins (GPIO). In order to abstract
the low-level architecture specific code from the high-level task
code, all GPIO events are implemented in architectures specific
code, all GPIO events are implemented in architecture specific
wrappers (eg, **src/avr/gpio.c**). The code is compiled with gcc's
"-flto -fwhole-program" optimization which does an excellent job of
inlining functions across compilation units, so most of these tiny
@@ -212,6 +214,162 @@ ToolHead and kinematic classes. It's this part of the code which
specifies the movements and their timings. The remaining parts of the
processing is mostly just communication and plumbing.
Adding a host module
====================
The Klippy host code has a dynamic module loading capability. If a
config section named "[my_module]" is found in the printer config file
then the software will automatically attempt to load the python module
klippy/extras/my_module.py . This module system is the preferred
method for adding new functionality to Klipper.
The easiest way to add a new module is to use an existing module as a
reference - see **klippy/extras/servo.py** as an example.
The following may also be useful:
* Execution of the module starts in the module level `load_config()`
function (for config sections of the form [my_module]) or in
`load_config_prefix()` (for config sections of the form
[my_module my_name]). This function is passed a "config" object and
it must return a new "printer object" associated with the given
config section.
* During the process of instantiating a new printer object, the config
object can be used to read parameters from the given config
section. This is done using `config.get()`, `config.getfloat()`,
`config.getint()`, etc. methods. Be sure to read all values from the
config during the construction of the printer object - if the user
specifies a config parameter that is not read during this phase then
it will be assumed it is a typo in the config and an error will be
raised.
* Use the `config.get_printer()` method to obtain a reference to the
main "printer" class. This "printer" class stores references to all
the "printer objects" that have been instantiated. Use the
`printer.lookup_object()` method to find references to other printer
objects. Almost all functionality (even core kinematic modules) are
encapsulated in one of these printer objects. Note, though, that
when a new module is instantiated, not all other printer objects
will have been instantiated. The "gcode" and "pins" modules will
always be available, but for other modules it is a good idea to
defer the lookup.
* Define a `printer_state()` method if the code needs to be called
during printer setup and/or shutdown. This method is called twice
during setup (with "connect" and then "ready") and may also be
called at run-time (with "shutdown" or "disconnect"). It is common
to perform "printer object" lookup during the "connect" and "ready"
phases.
* If there is an error in the user's config, be sure to raise it
during the `load_config()` or `printer_state("connect")` phases. Use
either `raise config.error("my error")` or `raise
printer.config_error("my error")` to report the error.
* Use the "pins" module to configure a pin on a micro-controller. This
is typically done with something similar to
`printer.lookup_object("pins").setup_pin("pwm",
config.get("my_pin"))`. The returned object can then be commanded at
run-time.
* If the module needs access to system timing or external file
descriptors then use `printer.get_reactor()` to obtain access to the
global "event reactor" class. This reactor class allows one to
schedule timers, wait for input on file descriptors, and to "sleep"
the host code.
* Do not use global variables. All state should be stored in the
printer object returned from the `load_config()` function. This is
important as otherwise the RESTART command may not perform as
expected. Also, for similar reasons, if any external files (or
sockets) are opened then be sure to close them from the
`printer_state("disconnect")` callback.
* Avoid accessing the internal member variables (or calling methods
that start with an underscore) of other printer objects. Observing
this convention makes it easier to manage future changes.
* If submitting the module for inclusion in the main Klipper code, be
sure to place a copyright notice at the top of the module. See the
existing modules for the preferred format.
Adding new kinematics
=====================
This section provides some tips on adding support to Klipper for
additional types of printer kinematics. This type of activity requires
excellent understanding of the math formulas for the target
kinematics. It also requires software development skills - though one
should only need to update the host software (which is written in
Python).
Useful steps:
1. Start by studying the [above section](#code-flow-of-a-move-command)
and the [Kinematics document](Kinematics.md).
2. Review the existing kinematic classes in cartesian.py, corexy.py,
and delta.py. The kinematic classes are tasked with converting a
move in cartesian coordinates to the movement on each stepper. One
should be able to copy one of these files as a starting point.
3. Implement the `get_postion()` method in the new kinematics
class. This method converts the current stepper position of each
stepper axis (stored in millimeters) to a position in cartesian
space (also in millimeters).
4. Implement the `set_postion()` method. This is the inverse of
get_position() - it sets each axis position (in millimeters) given
a position in cartesian coordinates.
5. Implement the `move()` method. The goal of the move() method is to
convert a move defined in cartesian space to a series of stepper
step times that implement the requested movement.
* The `move()` method is passed a "print_time" parameter (which
stores a time in seconds) and a "move" class instance that fully
defines the movement. The goal is to repeatedly invoke the
`stepper.step()` method with the time (relative to print_time)
that each stepper should step at to obtain the desired motion.
* One "trick" to help with the movement calculations is to imagine
there is a physical rail between `move.start_pos` and
`move.end_pos` that confines the print head so that it can only
move along this straight line of motion. Then, if the head is
confined to that imaginary rail, the head is at `move.start_pos`,
only one stepper is enabled (all other steppers can move freely),
and the given stepper is stepped a single step, then one can
imagine that the head will move along the line of movement some
distance. Determine the formula converting this step distance to
distance along the line of movement. Once one has the distance
along the line of movement, one can figure out the time that the
head should be at that position (using the standard formulas for
velocity and acceleration). This time is the ideal step time for
the given stepper and it can be passed to the `stepper.step()`
method.
* The `stepper.step()` method must always be called with an
increasing time for a given stepper (steps must be scheduled in
the order they are to be executed). A common error during
kinematic development is to receive an "Internal error in
stepcompress" failure - this is generally due to the step()
method being invoked with a time earlier than the last scheduled
step. For example, if the last step in move1 is scheduled at a
time greater than the first step in move2 it will generally
result in the above error.
* Fractional steps. Be aware that a move request is given in
cartesian space and it is not confined to discreet
locations. Thus a move's start and end locations may translate to
a location on a stepper axis that is between two steps (a
fractional step). The code must handle this. The preferred
approach is to schedule the next step at the time a move would
position the stepper axis at least half way towards the next
possible step location. Incorrect handling of fractional steps is
a common cause of "Internal error in stepcompress" failures.
6. Other methods. The `home()`, `check_move()`, and other methods
should also be implemented. However, at the start of development
one can use empty code here.
7. Implement test cases. Create a g-code file with a series of moves
that can test important cases for the given kinematics. Follow the
[debugging documentation](Debugging.md) to convert this g-code file
to micro-controller commands. This is useful to exercise corner
cases and to check for regressions.
8. Optimize if needed. One may notice that the existing kinematic
classes do not call `stepper.step()`. This is purely an
optimization - the inner loop of the kinematic calculations were
moved to C to reduce load on the host cpu. All of the existing
kinematic classes started development using `stepper.step()` and
then were later optimized. The g-code to mcu command translation
(described in the previous step) is a useful tool during
optimization - if a code change is purely an optimization then it
should not impact the resulting text representation of the mcu
commands (though minor changes in output due to floating point
rounding are possible). So, one can use this system to detect
regressions.
Time
====

147
docs/Config_checks.md Normal file
View File

@@ -0,0 +1,147 @@
This document provides a list of steps to help confirm the pin
settings in the Klipper printer.cfg file. It is a good idea to run
through these steps after following the steps in the
[installation document](Installation.md).
During this guide, it may be necessary to make changes to the Klipper
config file. Be sure to issue a RESTART command after every change to
the config file to ensure that the change takes effect (type "restart"
in the Octoprint terminal tab and then click "Send"). It's also a good
idea to issue a STATUS command after every RESTART to verify that the
config file is successfully loaded.
### Verify temperature
Start by verifying that temperatures are being properly
reported. Navigate to the Octoprint temperature tab.
![octoprint-temperature](img/octoprint-temperature.png)
Verify that the temperature of the nozzle and bed (if applicable) are
present and not increasing. If it is increasing, remove power from the
printer. If the temperatures are not accurate, review the
"sensor_type" and "sensor_pin" settings for the nozzle and/or bed.
### Verify M112
Navigate to the Octoprint terminal tab and issue an M112 command in
the terminal box. This command requests Klipper to go into a
"shutdown" state. It will cause Octoprint to disconnect from Klipper -
navigate to the Connection area and click on "Connect" to cause
Octoprint to reconnect. Then navigate to the Octoprint temperature tab
and verify that temperatures continue to update and the temperatures
are not increasing. If temperatures are increasing, remove power from
the printer.
The M112 command causes Klipper to go into a "shutdown" state. To
clear this state, issue a FIRMWARE_RESTART command in the Octoprint
terminal tab.
### Verify heaters
Navigate to the Octoprint temperature tab and type in 50 followed by
enter in the "Tool" temperature box. The extruder temperature in the
graph should start to increase (within about 30 seconds or so). Then
go to the "Tool" temperature drop-down box and select "Off". After
several minutes the temperature should start to return to its initial
room temperature value. If the temperature does not increase then
verify the "heater_pin" setting in the config.
If the printer has a heated bed then perform the above test again with
the bed.
### Verify stepper motor enable pin
Verify that all of the printer axes can manually move freely (the
stepper motors are disabled). If not, issue an M84 command to disable
the motors. If any of the axes still can not move freely, then verify
the stepper "enable_pin" configuration for the given axis. On most
commodity stepper motor drivers, the motor enable pin is "active low"
and therefore the enable pin should have a "!" before the pin (for
example, "enable_pin: !ar38").
### Verify endstops
Manually move all the printer axes so that none of them are in contact
with an endstop. Send a QUERY_ENDSTOPS command via the Octoprint
terminal tab. It should respond with the current state of all of the
configured endstops and they should all report a state of "open". For
each of the endstops, rerun the QUERY_ENDSTOPS command while manually
triggering the endstop. The QUERY_ENDSTOPS command should report the
endstop as "TRIGGERED".
If the endstop appears inverted (it reports "open" when triggered and
vice-versa) then add a "!" to the pin definition (for example,
"endstop_pin: ^!ar3"), or remove the "!" if there is already one
present.
If the endstop does not change at all then it generally indicates that
the endstop is connected to a different pin. However, it may also
require a change to the pullup setting of the pin (the '^' at the
start of the endstop_pin name - most printers will use a pullup
resistor and the '^' should be present).
### Verify stepper motor direction
Make sure the printer.cfg file does not have "homing_speed" set for
any axis (or set it to a value of 5 or less).
On cartesian style printers, manually move the X axis to a midway
point, issue a G28X0 command, and verify that the X motor moves slowly
towards the endstop defined for that axis. If the motor moves in the
wrong direction issue an M112 command to abort the move. A wrong
direction generally indicates that the "dir_pin" for the axis needs to
be inverted. This is done by adding a '!' to the "dir_pin" in the
printer config file (or removing it if one is already there). For
example, change "dir_pin: xyz" to "dir_pin: !xyz". Then RESTART and
retest the axis. If the axis does not move at all, then verify the
"enable_pin" and "step_pin" settings for the axis. For cartesian style
printers, repeat the test for the Y and Z axis with G28Y0 and G28Z0.
For delta style printers, manually move all three carriages to a
midway point and then issue a G28 command. Verify all three motors
move simultaneously upwards. If not, issue an M112 command and follow
the troubleshooting steps in the preceding paragraph.
### Verify extruder motor
To test the extruder motor it will be necessary to heat the extruder
to a printing temperature. Navigate to the Octoprint temperature tab
and select a target temperature from the temperature drop-down box (or
manually enter an appropriate temperature). Wait for the printer to
reach the desired temperature. Then navigate to the Octoprint control
tab and click the "Extrude" button. Verify that the extruder motor
turns in the correct direction. If it does not, see the
troubleshooting tips in the previous section to confirm the
"enable_pin", "step_pin", and "dir_pin" settings for the extruder.
### Calibrate PID settings
Klipper supports
[PID control](https://en.wikipedia.org/wiki/PID_controller) for the
extruder and bed heaters. In order to use this control mechanism it is
necessary to calibrate the PID settings on each printer. (PID settings
found in other firmwares or in the example configuration files often
work poorly.)
To calibrate the extruder, navigate to the OctoPrint terminal tab and
run the PID_CALIBRATE command. For example: `PID_CALIBRATE
HEATER=extruder TARGET=170`
At the completion of the tuning test, update the printer.cfg file with
the recommended pid_Kp, pid_Ki, and pid_Kd values.
If the printer has a heated bed and it supports being driven by PWM
(Pulse Width Modulation) then it is recommended to use PID control for
the bed. (When the bed heater is controlled using the PID algorithm it
may turn on and off ten times a second, which may not be suitable for
heaters using a mechanical switch.) A typical bed PID calibration
command is: `PID_CALIBRATE HEATER=heater_bed TARGET=60`
### Next steps
This guide is intended to help with basic verification of pin settings
in the Klipper configuration file. It may be necessary to perform
detailed printer calibration - a number of guides are available online
to help with this (for example, do a web search for "3d printer
calibration").

View File

@@ -1,25 +1,29 @@
This page provides information on how to contact the Klipper
developers.
Bug reporting
=============
Issue reporting
===============
Bug reports are submitted through github issues. All bug reports must
In order to report a problem or request a change in behavior, it is
necessary to collect the Klipper log file. The first step is to
**issue an M112 command** in the OctoPrint terminal window immediately
after the undesirable event occurs. This causes Klipper to go into a
"shutdown state" and it will cause additional debugging information to
be written to the log file.
Issue requests are submitted through Github. **All issues must
include the full /tmp/klippy.log log file from the session that
produced the error. To acquire this log file, ssh into the computer
running the klipper host software, and run:
produced the error.** An "scp" and/or "sftp" utility is needed to
acquire this log file. The "scp" utility comes standard with Linux and
MacOS desktops. There are freely available scp utilities for other
desktops (eg, WinSCP).
```
gzip -k /tmp/klippy.log
```
Then scp the resulting `/tmp/klippy.log.gz` file from the host machine
to your desktop. (If your desktop does not have scp installed, there
are a number of free scp programs available - just do a web search for
`windows scp` to find one.) Open a new issue at
https://github.com/KevinOConnor/klipper/issues , attach the
`klippy.log.gz` file to that issue, and provide a description of the
problem.
Use the scp utility to copy the `/tmp/klippy.log` file from the host
machine to your desktop. It is a good idea to compress the klippy.log
file before posting it (eg, using zip or gzip). Open a new issue at
https://github.com/KevinOConnor/klipper/issues , provide a description
of the problem, and **attach the `klippy.log` file to the issue**:
![attach-issue](img/attach-issue.png)
Mailing list
============

View File

@@ -115,17 +115,20 @@ gtkwave avrsim.vcd
```
Manually sending commands to the micro-controller
-------------------------------------------------
=================================================
Normally, Klippy would be used to translate gcode commands to Klipper
commands. However, it's also possible to manually send Klipper
commands (functions marked with the DECL_COMMAND() macro in the
Klipper source code). To do so, run:
Normally, the host klippy.py process would be used to translate gcode
commands to Klipper micro-controller commands. However, it's also
possible to manually send these MCU commands (functions marked with
the DECL_COMMAND() macro in the Klipper source code). To do so, run:
```
~/klippy-env/bin/python ./klippy/console.py /tmp/pseudoserial 250000
```
See the "HELP" command within the tool for more information on its
functionality.
Generating load graphs
======================
@@ -148,3 +151,23 @@ Then graphs can be produced with:
```
One can then view the resulting **loadgraph.png** file.
Extracting information from the klippy.log file
===============================================
The Klippy log file (/tmp/klippy.log) also contains debugging
information. There is a logextract.py script that may be useful when
analyzing a micro-controller shutdown or similar problem. It is
typically run with something like:
```
mkdir work_directory
cd work_directory
cp /tmp/klippy.log .
~/klipper/scripts/logextract.py ./klippy.log
```
The script will extract the printer config file and will extract MCU
shutdown information. The information dumps from an MCU shutdown (if
present) will be reordered by timestamp to assist in diagnosing cause
and effect scenarios.

252
docs/FAQ.md Normal file
View File

@@ -0,0 +1,252 @@
Frequently asked questions
==========================
1. [How can I donate to the project?](#how-can-i-donate-to-the-project)
2. [How do I calculate the step_distance parameter in the printer config file?](#how-do-i-calculate-the-step_distance-parameter-in-the-printer-config-file)
3. [Where's my serial port?](#wheres-my-serial-port)
4. [The "make flash" command doesn't work](#the-make-flash-command-doesnt-work)
5. [How do I change the serial baud rate?](#how-do-i-change-the-serial-baud-rate)
6. [Can I run Klipper on something other than a Raspberry Pi 3?](#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
7. [Why can't I move the stepper before homing the printer?](#why-cant-i-move-the-stepper-before-homing-the-printer)
8. [Why is the Z position_endstop set to 0.5 in the default configs?](#why-is-the-z-position_endstop-set-to-05-in-the-default-configs)
9. [I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis](#i-converted-my-config-from-marlin-and-the-xy-axes-work-fine-but-i-just-get-a-screeching-noise-when-homing-the-z-axis)
10. [When I set "restart_method=command" my AVR device just hangs on a restart](#when-i-set-restart_methodcommand-my-avr-device-just-hangs-on-a-restart)
11. [Will the heaters be left on if the Raspberry Pi crashes?](#will-the-heaters-be-left-on-if-the-raspberry-pi-crashes)
12. [How do I upgrade to the latest software?](#how-do-i-upgrade-to-the-latest-software)
### How can I donate to the project?
Thanks. Kevin has a Patreon page at: https://www.patreon.com/koconnor
### How do I calculate the step_distance parameter in the printer config file?
If you know the steps per millimeter for the axis then use a
calculator to divide 1.0 by steps_per_mm. Then round this number to
six decimal places and place it in the config (six decimal places is
nano-meter precision).
The step_distance defines the distance that the axis will travel on
each motor driver pulse. It can also be calculated from the axis
pitch, motor step angle, and driver microstepping. If unsure, do a web
search for "calculate steps per mm" to find an online calculator.
### Where's my serial port?
The general way to find a USB serial port is to run `ls -l
/dev/serial/by-id/` from an ssh terminal on the host machine. It will
likely produce output similar to the following:
```
lrwxrwxrwx 1 root root 13 Jan 3 22:15 usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00 -> ../../ttyACM0
```
The name found in the above command is stable and it is possible to
use it in the config file and while flashing the micro-controller
code. For example, a flash command might look similar to:
```
sudo service klipper stop
make flash FLASH_DEVICE=/dev/serial/by-id/usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00
sudo service klipper start
```
and the updated config might look like:
```
[mcu]
serial: /dev/serial/by-id/usb-UltiMachine__ultimachine.com__RAMBo_12345678912345678912-if00
```
Be sure to copy-and-paste the name from the "ls" command that you ran
above as the name will be different for each printer.
### The "make flash" command doesn't work
The code attempts to flash the device using the most common method for
each platform. Unfortunately, there is a lot of variance in flashing
methods, so the "make flash" command may not work on all boards.
If you're having an intermittent failure or you do have a standard
setup, then double check that Klipper isn't running when flashing
(sudo service klipper stop), make sure OctoPrint isn't trying to
connect directly to the device (open the Connection tab in the web
page and click Disconnect if the Serial Port is set to the device),
and make sure FLASH_DEVICE is set correctly for your board (see the
[question above](#wheres-my-serial-port)).
However, if "make flash" just doesn't work for your board, then you
will need to manually flash. See if there is a config file in the
[config directory](../config) with specific instructions for flashing
the device. Also, check the board manufacturer's documentation to see
if it describes how to flash the device. Finally, on AVR devices, it
may be possible to manually flash the device using
[avrdude](http://www.nongnu.org/avrdude/) with custom command-line
parameters - see the avrdude documentation for further information.
### How do I change the serial baud rate?
The default baud rate is 250000 in both the Klipper micro-controller
configuration and in the Klipper host software. This works on almost
all micro-controllers and it is the recommended setting. (Most online
guides that refer to a baud rate of 115200 are outdated.)
If you need to change the baud rate, then the new rate will need to be
configured in the micro-controller (during **make menuconfig**) and
that updated code will need to be flashed to the micro-controller. The
Klipper printer.cfg file will also need to be updated to match that
baud rate (see the example.cfg file for details). For example:
```
[mcu]
baud: 250000
```
The baud rate shown on the OctoPrint web page has no impact on the
internal Klipper micro-controller baud rate. Always set the OctoPrint
baud rate to 250000 when using Klipper.
### Can I run Klipper on something other than a Raspberry Pi 3?
The recommended hardware is a Raspberry Pi 2 or a Raspberry
Pi 3.
Klipper will run on a Raspberry Pi 1 and on the Raspberry Pi Zero, but
these boards don't have enough processing power to run OctoPrint
well. It's not uncommon for print stalls to occur on these slower
machines (the printer may move faster than OctoPrint can send movement
commands) when printing directly from OctoPrint. If you wish to run on
one one of these slower boards anyway, consider using the
"virtual_sdcard" feature (see
[config/example-extras.cfg](../config/example-extras.cfg) for details)
when printing.
For running on the Beaglebone, see the
[Beaglebone specific installation instructions](beaglebone.md).
Klipper has been run on other machines. The Klipper host software
only requires Python running on a Linux (or similar)
computer. However, if you wish to run it on a different machine you
will need Linux admin knowledge to install the system prerequisites
for that particular machine. See the
[install-octopi.sh](../scripts/install-octopi.sh) script for further
information on the necessary Linux admin steps.
### Why can't I move the stepper before homing the printer?
The code does this to reduce the chance of accidentally commanding the
head into the bed or a wall. Once the printer is homed the software
attempts to verify each move is within the position_min/max defined in
the config file. If the motors are disabled (via an M84 or M18
command) then the motors will need to be homed again prior to
movement.
If you want to move the head after canceling a print via OctoPrint,
consider changing the OctoPrint cancel sequence to do that for
you. It's configured in OctoPrint via a web browser under:
Settings->GCODE Scripts
If you want to move the head after a print finishes, consider adding
the desired movement to the "custom g-code" section of your slicer.
### Why is the Z position_endstop set to 0.5 in the default configs?
For cartesian style printers the Z position_endstop specifies how far
the nozzle is from the bed when the endstop triggers. If possible, it
is recommended to use a Z-max endstop and home away from the bed (as
this reduces the potential for bed collisions). However, if one must
home towards the bed then it is recommended to position the endstop so
it triggers when the nozzle is still a small distance away from the
bed. This way, when homing the axis, it will stop before the nozzle
touches the bed.
Almost all mechanical switches can still move a small distance
(eg, 0.5mm) after they are triggered. So, for example, if the
position_endstop is set to 0.5mm then one may still command the
printer to move to Z0.2. The position_min config setting (which
defaults to 0) is used to specify the minimum Z position one may
command the printer to move to.
Note, the Z position_endstop specifies the distance from the nozzle to
the bed when the nozzle and bed (if applicable) are hot. It is typical
for thermal expansion to cause nozzle expansion of around .1mm, which
is also the typical thickness of a sheet of printer paper. Thus, it is
common to use the "paper test" to confirm calibration of the Z
height - check that the bed and nozzle are at room temperature, check
that there is no plastic on the head or bed, home the printer, place a
piece of paper between the nozzle and bed, and repeatedly command the
head to move closer to the bed checking each time if you feel a small
amount of friction when sliding the paper between bed and nozzle - if
all is calibrated well a small amount of friction would be felt when
the height is at Z0.
### I converted my config from Marlin and the X/Y axes work fine, but I just get a screeching noise when homing the Z axis
Short answer: Try reducing the max_z_velocity setting in the printer
config. Also, if the Z stepper is moving in the wrong direction, try
inverting the dir_pin setting in the config (eg, "dir_pin: !xyz"
instead of "dir_pin: xyz").
Long answer: In practice Marlin can typically only step at a rate of
around 10000 steps per second. If it is requested to move at a speed
that would require a higher step rate then Marlin will generally just
step as fast as it can. Klipper is able to achieve much higher step
rates, but the stepper motor may not have sufficient torque to move at
a higher speed. So, for a Z axis with a very precise step_distance the
actual obtainable max_z_velocity may be smaller than what is
configured in Marlin.
### When I set "restart_method=command" my AVR device just hangs on a restart
Some old versions of the AVR bootloader have a known bug in watchdog
event handling. This typically manifests when the printer.cfg file has
restart_method set to "command". When the bug occurs, the AVR device
will be unresponsive until power is removed and reapplied to the
device (the power or status LEDs may also blink repeatedly until the
power is removed).
The workaround is to use a restart_method other than "command" or to
flash an updated bootloader to the AVR device. Flashing a new
bootloader is a one time step that typically requires an external
programmer - search the web to find the instructions for your
particular device.
### Will the heaters be left on if the Raspberry Pi crashes?
The software has been designed to prevent that. Once the host enables
a heater, the host software needs to confirm that enablement every 5
seconds. If the micro-controller does not receive a confirmation every
5 seconds it goes into a "shutdown" state which is designed to turn
off all heaters and stepper motors.
See the "config_digital_out" command in the
[MCU commands](MCU_Commands.md) document for further details.
### How do I upgrade to the latest software?
The general way to upgrade is to ssh into the Raspberry Pi and run:
```
cd ~/klipper
git pull
~/klipper/scripts/install-octopi.sh
```
Then one can recompile and flash the micro-controller code. For
example:
```
sudo service klipper stop
make flash FLASH_DEVICE=/dev/ttyACM0
sudo service klipper start
```
However, it's often the case that only the host software changes. In
this case, one can update and restart just the host software with:
```
cd ~/klipper
git pull
sudo service klipper restart
```
If after using this shortcut the software warns about needing to
reflash the micro-controller or some other unusual error occurs, then
follow the full upgrade steps outlined above. Note that the RESTART
and FIRMWARE_RESTART g-code commands do not load new software - the
above "sudo service klipper restart" and "make flash" commands are
needed for a software change to take effect.

131
docs/G-Codes.md Normal file
View File

@@ -0,0 +1,131 @@
This document describes the commands that Klipper supports. These are
commands that one may enter into the OctoPrint terminal tab.
# G-Code commands
Klipper supports the following standard G-Code commands:
- Move (G0 or G1): `G1 [X<pos>] [Y<pos>] [Z<pos>] [E<pos>] [F<speed>]`
- Dwell: `G4 P<milliseconds>`
- Move to origin: `G28 [X] [Y] [Z]`
- Turn off motors: `M18` or `M84`
- Wait for current moves to finish: `M400`
- Select tool: `T<index>`
- Use absolute/relative distances for extrusion: `M82`, `M83`
- Use absolute/relative coordinates: `G90`, `G91`
- Set position: `G92 [X<pos>] [Y<pos>] [Z<pos>] [E<pos>]`
- Set speed factor override percentage: `M220 S<percent>`
- Set extrude factor override percentage: `M221 S<percent>`
- Get extruder temperature: `M105`
- Set extruder temperature: `M104 [T<index>] [S<temperature>]`
- Set extruder temperature and wait: `M109 [T<index>] S<temperature>`
- Set bed temperature: `M140 [S<temperature>]`
- Set bed temperature and wait: `M190 S<temperature>`
- Set fan speed: `M106 S<value>`
- Turn fan off: `M107`
- Emergency stop: `M112`
- Get current position: `M114`
- Get firmware version: `M115`
- Set home offset: `M206 [X<pos>] [Y<pos>] [Z<pos>]`
For further details on the above commands see the
[RepRap G-Code documentation](http://reprap.org/wiki/G-code).
Klipper's goal is to support the G-Code commands produced by common
3rd party software (eg, OctoPrint, Printrun, Slic3r, Cura, etc.) in
their standard configurations. It is not a goal to support every
possible G-Code command. Instead, Klipper prefers human readable
["extended G-Code commands"](#extended-g-code-commands).
## G-Code SD card commands
Klipper also supports the following standard G-Code commands if the
"virtual_sdcard" config section is enabled:
- List SD card: `M20`
- Initialize SD card: `M21`
- Select SD file: `M23 <filename>`
- Start/resume SD print: `M24`
- Pause SD print: `M25`
- Set SD position: `M26 S<offset>`
- Report SD print status: `M27`
# Extended G-Code Commands
Klipper uses "extended" G-Code commands for general configuration and
status. These extended commands all follow a similar format - they
start with a command name and may be followed by one or more
parameters. For example: `SET_SERVO SERVO=myservo ANGLE=5.3`. In this
document, the commands and parameters are shown in uppercase, however
they are not case sensitive. (So, "SET_SERVO" and "set_servo" both run
the same command.)
The following standard commands are supported:
- `QUERY_ENDSTOPS`: Probe the axis endstops and report if they are
"triggered" or in an "open" state. This command is typically used to
verify that an endstop is working correctly.
- `GET_POSITION`: Return information on the current location of the
toolhead.
- `PID_CALIBRATE HEATER=<config_name> TARGET=<temperature>
[WRITE_FILE=1]`: Perform a PID calibration test. The specified
heater will be enabled until the specified target temperature is
reached, and then the heater will be turned off and on for several
cycles. If the WRITE_FILE parameter is enabled, then the file
/tmp/heattest.txt will be created with a log of all temperature
samples taken during the test.
- `RESTART`: This will cause the host software to reload its config
and perform an internal reset. This command will not clear error
state from the micro-controller (see FIRMWARE_RESTART) nor will it
load new software (see
[the FAQ](FAQ.md#how-do-i-upgrade-to-the-latest-software)).
- `FIRMWARE_RESTART`: This is similar to a RESTART command, but it
also clears any error state from the micro-controller.
- `STATUS`: Report the Klipper host software status.
- `HELP`: Report the list of available extended G-Code commands.
## Custom Pin Commands
The following command is available when an "output_pin" config section
is enabled:
- `SET_PIN PIN=config_name VALUE=<value>`
## Servo Commands
The following commands are available when a "servo" config section is
enabled:
- `SET_SERVO SERVO=config_name WIDTH=<seconds>`
- `SET_SERVO SERVO=config_name ANGLE=<degrees>`
## Probe
The following commands are available when a "probe" config section is
enabled:
- `PROBE`: Move the nozzle downwards until the probe triggers.
- `QUERY_PROBE`: Report the current status of the probe ("triggered"
or "open").
## Delta Calibration
The following commands are available when the "delta_calibrate" config
section is enabled:
- `DELTA_CALIBRATE`: This command will probe seven points on the bed
and recommend updated endstop positions, tower angles, and radius.
- `NEXT`: If manual bed probing is enabled, then one can use this
command to move to the next probing point during a DELTA_CALIBRATE
operation.
## Bed Tilt
The following commands are available when the "bed_tilt" config
section is enabled:
- `BED_TILT_CALIBRATE`: This command will probe the points specified
in the config and then recommend updated x and y tilt adjustments.
- `NEXT`: If manual bed probing is enabled, then one can use this
command to move to the next probing point during a
BED_TILT_CALIBRATE operation.
## Dual Carriages
The following command is available when the "dual_carriage" config
section is enabled:
- `SET_DUAL_CARRIAGE CARRIAGE=[0|1]`: This command will set the active
carriage. It is typically invoked from the activate_gcode and
deactivate_gcode fields in a multiple extruder configuration.

View File

@@ -1,13 +1,9 @@
These instructions assume the software will run on a Raspberry Pi
computer in conjunction with OctoPrint. (See the
[Beaglebone specific instructions](beaglebone.md) if using a
Beaglebone.) It is recommended that a Raspberry Pi 2 or Raspberry Pi 3
computer be used as the host machine.
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.
computer in conjunction with OctoPrint. It is recommended that a
Raspberry Pi 2 or Raspberry Pi 3 computer be used as the host machine
(see the
[FAQ](FAQ.md#can-i-run-klipper-on-something-other-than-a-raspberry-pi-3)
for other machines).
Klipper currently supports Atmel ATmega based micro-controllers,
Arduino Due (Atmel SAM3x8e ARM micro-controller), and
@@ -23,9 +19,14 @@ 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.5 or later.
After installing OctoPi and upgrading OctoPrint, ssh into the target
machine (ssh pi@octopi -- password is "raspberry") and run the
following commands:
After installing OctoPi and upgrading OctoPrint, it will be necessary
to ssh into the target machine to run a handful of system commands. If
using a Linux or MacOS desktop, then the "ssh" software should already
be installed on the desktop. There are free ssh clients available for
other desktops (eg,
[PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/)). Use the
ssh utility to connect to the Raspberry Pi (ssh pi@octopi -- password
is "raspberry") and run the following commands:
```
git clone https://github.com/KevinOConnor/klipper
@@ -40,15 +41,18 @@ minutes to complete.
Building and flashing the micro-controller
==========================================
To compile the micro-controller code, start by configuring it:
To compile the micro-controller code, start by running these commands
on the Raspberry Pi:
```
cd ~/klipper/
make menuconfig
```
Select the appropriate micro-controller and serial baud rate. Once
configured, run:
Select the appropriate micro-controller and review any other options
provided. For boards with serial ports, the default baud rate is
250000 (see the [FAQ](FAQ.md#how-do-i-change-the-serial-baud-rate) if
changing). Once configured, run:
```
make
@@ -62,25 +66,11 @@ make flash FLASH_DEVICE=/dev/ttyACM0
sudo service klipper start
```
Configuring Klipper
===================
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
```
Make sure to review and update each setting that is appropriate for
the hardware.
When flashing for the first time, make sure that OctoPrint is not
connected directly to the printer (from the OctoPrint web page, under
the "Connection" section, click "Disconnect"). The most common
communication device is **/dev/ttyACM0** - see the
[FAQ](FAQ.md#wheres-my-serial-port) for other possibilities.
Configuring OctoPrint to use Klipper
====================================
@@ -105,13 +95,47 @@ 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". 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.
config file - that means OctoPrint is successfully communicating with
Klipper. Proceed to the next section.
Configuring Klipper
===================
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.
Arguably the easiest way to update the Klipper configuration file is
to use a desktop editor that supports editing files over the "scp"
and/or "sftp" protocols. There are freely available tools that support
this (eg, Notepad++, WinSCP, and Cyberduck). Use one of the example
config files as a starting point and save it as a file named
"printer.cfg" in the home directory of the pi user (ie,
/home/pi/printer.cfg).
Alternatively, one can also copy and edit the file directly on the
Raspberry Pi via ssh - for example:
```
cp ~/klipper/config/example.cfg ~/printer.cfg
nano ~/printer.cfg
```
Make sure to review and update each setting that is appropriate for
the hardware.
After creating and editing the file it will be necessary to issue a
"restart" command in the OctoPrint web 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.
Klipper reports error messages via the OctoPrint terminal tab. The
"status" command can be used to re-report error messages. The default
@@ -122,7 +146,13 @@ 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.
After Klipper reports that the "printer is ready" go on to the
[config check document](Config_checks.md) to perform some basic checks
on the pin definitions in the config file.
Contacting the developers
=========================
See the [contact page](Contact.md) to ask questions or report a bug.
Be sure to see the [FAQ](FAQ.md) for answers to some common questions.
See the [contact page](Contact.md) to report a bug or to contact the
developers.

View File

@@ -4,7 +4,9 @@ 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.
[installation](Installation.md) to get started with Klipper. See
[config checks](Config_checks.md) for a guide to verify basic pin
settings in the config file.
The Klipper configuration is stored in a simple text file on the host
machine. The [config/example.cfg](../config/example.cfg) file serves
@@ -13,7 +15,9 @@ as a reference for the config file. The
on tuning the pressure advance config.
The [kinematics](Kinematics.md) document provides some technical
details on how Klipper implements motion.
details on how Klipper implements motion. The [FAQ](FAQ.md) answers
some common questions. The [G-Codes](G-Codes.md) document lists
currently supported run-time commands.
The history of Klipper releases is available at
[releases](Releases.md). See [contact](Contact.md) for information on
@@ -25,7 +29,8 @@ Developer Documentation
There are also several documents available for developers interested
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.
on the structure and layout of the Klipper code. See the
[contributing](CONTRIBUTING.md) document to submit improvements to Klipper.
See [protocol](Protocol.md) for information on the low-level messaging
protocol between host and micro-controller. See also

View File

@@ -63,7 +63,10 @@ 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).
end usually only with bowden extruders). If there is no significant
improvement seen after increasing pressure_advance to 0.20, then
pressure advance is unlikely to improve the quality of prints. Return
to a default configuration with pressure_advance disabled.
It is not unusual for one corner of the test print to be consistently
different than the other three corners. This typically occurs when the

View File

@@ -1,6 +1,37 @@
History of Klipper releases. Please see
[installation](Installation.md) for information on installing Klipper.
Klipper 0.6.0
=============
Available on 20180331. Major changes in this release:
* Enhanced heater and thermistor hardware failure checks
* Support for Z probes
* Initial support for automatic parameter calibration on deltas (via a
new delta_calibrate command)
* Initial support for bed tilt compensation (via bed_tilt_calibrate
command)
* Initial support for "safe homing" and homing overrides
* Initial support for displaying status on RepRapDiscount style 2004
and 12864 displays
* New multi-extruder improvements:
* Support for shared heaters
* Initial support for dual carriages
* Support for configuring multiple steppers per axis (eg, dual Z)
* Support for custom digital and pwm output pins (with a new SET_PIN command)
* Initial support for a "virtual sdcard" that allows printing directly
from Klipper (helps on machines too slow to run OctoPrint well)
* Support for setting different arm lengths on each tower of a delta
* Support for G-Code M220/M221 commands (speed factor override /
extrude factor override)
* Several documentation updates:
* Many new example config files for common off-the-shelf printers
* New multiple MCU config example
* New bltouch sensor config example
* New FAQ, config check, and G-Code documents
* Initial support for continuous integration testing on all github commits
* Several bug fixes and code cleanups
Klipper 0.5.0
=============

View File

@@ -44,10 +44,6 @@ Safety features
endstop detection is a good idea because of spurious signals caused
by electrical noise.)
* Support validating that heaters are heating at expected rates. This
can be useful to detect a sensor failure (eg, thermistor short) that
could otherwise cause the PID to command excessive heating.
Testing features
================
@@ -71,10 +67,6 @@ Hardware features
* Support for additional kinematics: scara, etc.
* Support shared motor enable GPIO lines.
* Support for bed-level probes.
* Possible support for touch panels attached to the micro-controller.
(In general, it would be preferable to attach touch panels to the
host system and have octoprint interact with the panel directly, but

View File

@@ -54,7 +54,6 @@ sudo systemctl start octoprint
Make sure the octoprint web server is accessible - it should be at:
[http://beaglebone:5000/](http://beaglebone:5000/)
Building the micro-controller code
==================================
@@ -92,3 +91,13 @@ Remaining configuration
Complete the installation by configuring Klipper and Octoprint
following the instructions in
[the main installation document](Installation.md#configuring-klipper).
Printing on the Beaglebone
==========================
Unfortunately, the Beaglebone processor can sometimes struggle to run
OctoPrint well. Print stalls have been known to occur on complex
prints (the printer may move faster than OctoPrint can send movement
commands). If this occurs, consider using the "virtual_sdcard" feature
(see [config/example-extras.cfg](../config/example-extras.cfg) for
details) to print directly from Klipper.

View File

@@ -0,0 +1,37 @@
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
1 Letterman Drive
Suite D4700
San Francisco, CA, 94129
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.

BIN
docs/img/attach-issue.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

2
docs/issue_template.md Normal file
View File

@@ -0,0 +1,2 @@
<!-- Klipper do something undesirable? YOU MUST ATTACH THE KLIPPER LOG FILE.
See: https://github.com/KevinOConnor/klipper/blob/master/docs/Contact.md -->

View File

@@ -7,10 +7,39 @@ square_width = 5;
square_size = 60;
square_height = 5;
module hollow_square() {
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]);
translate([-.5, square_size/2 - 4, -1])
cube([1, 2, square_height+2]);
cube([square_size-2*square_width, square_size-2*square_width,
square_height+2]);
}
}
module notch() {
CUT = 0.01;
depth = .5;
width = 1;
translate([-depth, -width, -CUT])
cube([2*depth, 2*width, square_height + 2*CUT]);
}
module square_with_notches() {
difference() {
// Start with initial square
hollow_square();
// Remove four notches on inside perimeter
translate([square_width, square_size/2 - 4, 0])
notch();
translate([square_size/2, square_size - square_width, 0])
rotate([0, 0, 90])
notch();
translate([square_size - square_width, square_size/2, 0])
notch();
translate([square_size/2, square_width, 0])
rotate([0, 0, 90])
notch();
}
}
square_with_notches();

View File

@@ -2,20 +2,6 @@ solid OpenSCAD_Model
facet normal -1 0 0
outer loop
vertex 0 0 0
vertex 0 26 5
vertex 0 26 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 0 26 5
vertex 0 0 0
vertex 0 0 5
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 28 0
vertex 0 60 5
vertex 0 60 0
endloop
@@ -23,92 +9,176 @@ solid OpenSCAD_Model
facet normal -1 -0 0
outer loop
vertex 0 60 5
vertex 0 28 0
vertex 0 28 5
vertex 0 0 0
vertex 0 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 25 5
vertex 4.5 25 5
vertex 5 5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 29 5 5
vertex 5 5 5
vertex 29 4.5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 60 60 5
vertex 55 55 5
vertex 55.5 31 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
vertex 55.5 31 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 60 60 5
vertex 31 55.5 5
vertex 55 55 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 60 60 5
vertex 29 55.5 5
vertex 31 55.5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 29 55.5 5
vertex 5 55 5
vertex 0.5 28 5
vertex 5 5 5
vertex 29 55 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 60 5
vertex 5 55 5
vertex 29 55.5 5
vertex 60 60 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 28 5
vertex 5 55 5
vertex 4.5 27 5
vertex 5 27 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 0 5
vertex 4.5 27 5
vertex 0 60 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 5 55 5
vertex 0 28 5
vertex 0.5 28 5
vertex 0 60 5
vertex 4.5 27 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 29 55.5 5
vertex 0 60 5
vertex 5 55 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 55.5 29 5
vertex 60 0 5
vertex 55.5 31 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 55 5 5
vertex 55.5 29 5
vertex 55 29 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 55.5 29 5
vertex 55 5 5
vertex 60 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 31 4.5 5
vertex 55 5 5
vertex 31 5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 55 5 5
vertex 31 4.5 5
vertex 60 0 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 29 4.5 5
vertex 60 0 5
vertex 31 4.5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 0 5
vertex 29 4.5 5
vertex 5 5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4.5 27 5
vertex 0 0 5
vertex 4.5 25 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 29 4.5 5
vertex 0 0 5
vertex 60 0 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 4.5 25 5
vertex 0 0 5
vertex 5 5 5
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 55.5 31 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 0.5 26 5
vertex 5 5 5
vertex 0.5 28 5
endloop
endfacet
facet normal -0 0 1
outer loop
vertex 0 26 5
vertex 5 5 5
vertex 0.5 26 5
vertex 55 31 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 26 5
vertex 55 55 5
vertex 31 55.5 5
vertex 31 55 5
endloop
endfacet
facet normal 1 -0 0
@@ -141,86 +211,170 @@ solid OpenSCAD_Model
endfacet
facet normal 0 0 -1
outer loop
vertex 60 0 0
vertex 5 27 0
vertex 4.5 27 0
vertex 5 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 29 55 0
vertex 5 55 0
vertex 29 55.5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 60 0 0
vertex 55.5 29 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
vertex 55.5 29 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 60 0 0
vertex 31 4.5 0
vertex 55 5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0.5 28 0
vertex 60 0 0
vertex 29 4.5 0
vertex 31 4.5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 29 4.5 0
vertex 5 5 0
vertex 0.5 26 0
vertex 29 5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 0 0
vertex 5 5 0
vertex 29 4.5 0
vertex 60 0 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 26 0
vertex 5 5 0
vertex 4.5 25 0
vertex 5 25 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 4.5 25 0
vertex 0 0 0
vertex 4.5 27 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 5 5 0
vertex 0 0 0
vertex 4.5 25 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 29 4.5 0
vertex 0 0 0
vertex 5 5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 55.5 31 0
vertex 60 60 0
vertex 55.5 29 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 55 55 0
vertex 55.5 31 0
vertex 55 31 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 55.5 31 0
vertex 55 55 0
vertex 60 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 31 55.5 0
vertex 55 55 0
vertex 31 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 55 55 0
vertex 31 55.5 0
vertex 60 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 29 55.5 0
vertex 60 60 0
vertex 31 55.5 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 60 0
vertex 29 55.5 0
vertex 5 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 60 0
vertex 4.5 27 0
vertex 0 0 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 5 5 0
vertex 0 26 0
vertex 0.5 26 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 5 0
vertex 0.5 28 0
vertex 5 55 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 0 28 0
vertex 5 55 0
vertex 0.5 28 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 5 55 0
vertex 29 55.5 0
vertex 0 60 0
vertex 60 60 0
endloop
endfacet
facet normal 0 0 -1
outer loop
vertex 4.5 27 0
vertex 0 60 0
vertex 5 55 0
vertex 0 28 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 55.5 29 0
vertex 55 5 0
vertex 55 29 0
endloop
endfacet
facet normal -0 0 -1
outer loop
vertex 55 5 0
vertex 31 4.5 0
vertex 31 5 0
endloop
endfacet
facet normal 0 -1 0
@@ -240,6 +394,20 @@ solid OpenSCAD_Model
facet normal 1 -0 0
outer loop
vertex 5 5 5
vertex 5 25 0
vertex 5 25 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 5 25 0
vertex 5 5 5
vertex 5 5 0
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 5 27 5
vertex 5 55 0
vertex 5 55 5
endloop
@@ -247,13 +415,27 @@ solid OpenSCAD_Model
facet normal 1 0 0
outer loop
vertex 5 55 0
vertex 5 5 5
vertex 5 5 0
vertex 5 27 5
vertex 5 27 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 55 5 0
vertex 55 29 5
vertex 55 29 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 55 29 5
vertex 55 5 0
vertex 55 5 5
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 55 31 0
vertex 55 55 5
vertex 55 55 0
endloop
@@ -261,78 +443,232 @@ solid OpenSCAD_Model
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 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
vertex 55 31 0
vertex 55 31 5
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 55 5 0
vertex 29 5 0
vertex 5 5 5
vertex 55 5 5
vertex 29 5 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 5 5 5
vertex 55 5 0
vertex 29 5 0
vertex 5 5 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 55 5 0
vertex 31 5 5
vertex 55 5 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 31 5 5
vertex 55 5 0
vertex 31 5 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 5 55 0
vertex 29 55 5
vertex 5 55 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 29 55 5
vertex 5 55 0
vertex 29 55 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 31 55 0
vertex 55 55 5
vertex 31 55 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 55 55 5
vertex 31 55 0
vertex 55 55 0
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 4.5 25 5
vertex 4.5 27 0
vertex 4.5 27 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 4.5 27 0
vertex 4.5 25 5
vertex 4.5 25 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 4.5 27 0
vertex 5 27 5
vertex 4.5 27 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 5 27 5
vertex 4.5 27 0
vertex 5 27 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 5 25 0
vertex 4.5 25 5
vertex 5 25 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 4.5 25 5
vertex 5 25 0
vertex 4.5 25 0
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 29 55.5 0
vertex 31 55.5 5
vertex 29 55.5 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 31 55.5 5
vertex 29 55.5 0
vertex 31 55.5 0
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 29 55 5
vertex 29 55.5 0
vertex 29 55.5 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 29 55.5 0
vertex 29 55 5
vertex 29 55 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0.5 26 0
vertex 0.5 28 5
vertex 0.5 28 0
vertex 31 55 0
vertex 31 55.5 5
vertex 31 55.5 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 0.5 28 5
vertex 0.5 26 0
vertex 0.5 26 5
vertex 31 55.5 5
vertex 31 55 0
vertex 31 55 5
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 55.5 29 0
vertex 55.5 31 5
vertex 55.5 31 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 55.5 31 5
vertex 55.5 29 0
vertex 55.5 29 5
endloop
endfacet
facet normal 0 -1 0
outer loop
vertex 0 28 0
vertex 0.5 28 5
vertex 0 28 5
vertex 55 31 0
vertex 55.5 31 5
vertex 55 31 5
endloop
endfacet
facet normal 0 -1 -0
outer loop
vertex 0.5 28 5
vertex 0 28 0
vertex 0.5 28 0
vertex 55.5 31 5
vertex 55 31 0
vertex 55.5 31 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 0.5 26 0
vertex 0 26 5
vertex 0.5 26 5
vertex 55.5 29 0
vertex 55 29 5
vertex 55.5 29 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 0 26 5
vertex 0.5 26 0
vertex 0 26 0
vertex 55 29 5
vertex 55.5 29 0
vertex 55 29 0
endloop
endfacet
facet normal 0 1 -0
outer loop
vertex 31 4.5 0
vertex 29 4.5 5
vertex 31 4.5 5
endloop
endfacet
facet normal 0 1 0
outer loop
vertex 29 4.5 5
vertex 31 4.5 0
vertex 29 4.5 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 31 4.5 0
vertex 31 5 5
vertex 31 5 0
endloop
endfacet
facet normal -1 -0 0
outer loop
vertex 31 5 5
vertex 31 4.5 0
vertex 31 4.5 5
endloop
endfacet
facet normal 1 -0 0
outer loop
vertex 29 4.5 5
vertex 29 5 0
vertex 29 5 5
endloop
endfacet
facet normal 1 0 0
outer loop
vertex 29 5 0
vertex 29 4.5 5
vertex 29 4.5 0
endloop
endfacet
endsolid OpenSCAD_Model

View File

@@ -10,8 +10,9 @@ StepList = (0, 1, 2)
class CartKinematics:
def __init__(self, toolhead, printer, config):
self.steppers = [stepper.PrinterHomingStepper(
printer, config.getsection('stepper_' + n), n)
self.printer = printer
self.steppers = [stepper.LookupMultiHomingStepper(
printer, config.getsection('stepper_' + n))
for n in ['x', 'y', 'z']]
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_z_velocity = config.getfloat(
@@ -26,14 +27,34 @@ class CartKinematics:
self.steppers[1].set_max_jerk(max_halt_velocity, max_accel)
self.steppers[2].set_max_jerk(
min(max_halt_velocity, self.max_z_velocity), max_accel)
def set_position(self, newpos):
# Check for dual carriage support
self.dual_carriage_axis = None
self.dual_carriage_steppers = []
if config.has_section('dual_carriage'):
dc_config = config.getsection('dual_carriage')
self.dual_carriage_axis = dc_config.getchoice(
'axis', {'x': 0, 'y': 1})
dc_stepper = stepper.LookupMultiHomingStepper(printer, dc_config)
dc_stepper.set_max_jerk(max_halt_velocity, max_accel)
self.dual_carriage_steppers = [
self.steppers[self.dual_carriage_axis], dc_stepper]
printer.lookup_object('gcode').register_command(
'SET_DUAL_CARRIAGE', self.cmd_SET_DUAL_CARRIAGE,
desc=self.cmd_SET_DUAL_CARRIAGE_help)
def get_steppers(self, flags=""):
if flags == "Z":
return [self.steppers[2]]
return list(self.steppers)
def get_position(self):
return [s.mcu_stepper.get_commanded_position() for s in self.steppers]
def set_position(self, newpos, homing_axes):
for i in StepList:
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():
s = self.steppers[axis]
self.limits[axis] = (s.position_min, s.position_max)
s = self.steppers[i]
s.set_position(newpos[i])
if i in homing_axes:
self.limits[i] = (s.position_min, s.position_max)
def _home_axis(self, homing_state, axis, stepper):
s = stepper
# Determine moves
if s.homing_positive_dir:
pos = s.position_endstop - 1.5*(
@@ -46,28 +67,43 @@ class CartKinematics:
rpos = s.position_endstop + s.homing_retract_dist
r2pos = rpos + s.homing_retract_dist
# Initial homing
homing_speed = s.get_homing_speed()
homing_speed = s.homing_speed
if axis == 2:
homing_speed = min(homing_speed, self.max_z_velocity)
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], homing_speed)
homing_state.home(coord, homepos, s.get_endstops(), homing_speed)
# Retract
coord[axis] = rpos
homing_state.retract(list(coord), homing_speed)
homing_state.retract(coord, homing_speed)
# Home again
coord[axis] = r2pos
homing_state.home(
list(coord), homepos, [s], homing_speed/2.0, second_home=True)
homing_state.home(coord, homepos, s.get_endstops(),
homing_speed/2.0, second_home=True)
# Set final homed position
coord[axis] = s.position_endstop + s.get_homed_offset()
homing_state.set_homed_position(coord)
def query_endstops(self, print_time, query_flags):
return homing.query_endstops(print_time, query_flags, self.steppers)
def home(self, homing_state):
# Each axis is homed independently and in order
for axis in homing_state.get_axes():
if axis == self.dual_carriage_axis:
dc1, dc2 = self.dual_carriage_steppers
altc = self.steppers[axis] == dc2
self._activate_carriage(0)
self._home_axis(homing_state, axis, dc1)
self._activate_carriage(1)
self._home_axis(homing_state, axis, dc2)
self._activate_carriage(altc)
else:
self._home_axis(homing_state, axis, self.steppers[axis])
def motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
for stepper in self.steppers:
stepper.motor_enable(print_time, 0)
for stepper in self.dual_carriage_steppers:
stepper.motor_enable(print_time, 0)
self.need_motor_enable = True
def _check_motor_enable(self, print_time, move):
need_motor_enable = False
@@ -107,7 +143,7 @@ class CartKinematics:
axis_d = move.axes_d[i]
if not axis_d:
continue
mcu_stepper = self.steppers[i].mcu_stepper
step_const = self.steppers[i].step_const
move_time = print_time
start_pos = move.start_pos[i]
axis_r = abs(axis_d) / move.move_d
@@ -117,19 +153,38 @@ class CartKinematics:
# Acceleration steps
if move.accel_r:
accel_d = move.accel_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, accel_d, move.start_v * axis_r, accel)
step_const(move_time, start_pos, accel_d,
move.start_v * axis_r, accel)
start_pos += accel_d
move_time += move.accel_t
# Cruising steps
if move.cruise_r:
cruise_d = move.cruise_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, cruise_d, cruise_v, 0.)
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
move_time += move.cruise_t
# Deceleration steps
if move.decel_r:
decel_d = move.decel_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, decel_d, cruise_v, -accel)
step_const(move_time, start_pos, decel_d, cruise_v, -accel)
# Dual carriage support
def _activate_carriage(self, carriage):
toolhead = self.printer.lookup_object('toolhead')
toolhead.get_last_move_time()
dc_stepper = self.dual_carriage_steppers[carriage]
dc_axis = self.dual_carriage_axis
self.steppers[dc_axis] = dc_stepper
extruder_pos = toolhead.get_position()[3]
toolhead.set_position(self.get_position() + [extruder_pos])
if self.limits[dc_axis][0] <= self.limits[dc_axis][1]:
self.limits[dc_axis] = (
dc_stepper.position_min, dc_stepper.position_max)
self.need_motor_enable = True
cmd_SET_DUAL_CARRIAGE_help = "Set which carriage is active"
def cmd_SET_DUAL_CARRIAGE(self, params):
gcode = self.printer.lookup_object('gcode')
carriage = gcode.get_int('CARRIAGE', params)
if carriage not in (0, 1):
raise gcode.error("Invalid carriage")
self._activate_carriage(carriage)
gcode.reset_last_position()

View File

@@ -1,296 +0,0 @@
# Code to configure miscellaneous chips
#
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins, mcu
######################################################################
# Statically configured output pins
######################################################################
class PrinterStaticDigitalOut:
def __init__(self, printer, config):
pin_list = [pin.strip() for pin in config.get('pins').split(',')]
for pin_desc in pin_list:
mcu_pin = pins.setup_pin(printer, 'digital_out', pin_desc)
mcu_pin.setup_static()
class PrinterStaticPWM:
def __init__(self, printer, config):
mcu_pwm = pins.setup_pin(printer, 'pwm', config.get('pin'))
mcu_pwm.setup_max_duration(0.)
hard_pwm = config.getint('hard_pwm', None, minval=1)
if hard_pwm is None:
mcu_pwm.setup_cycle_time(config.getfloat(
'cycle_time', 0.100, above=0.))
else:
mcu_pwm.setup_hard_pwm(hard_pwm)
scale = config.getfloat('scale', 1., above=0.)
value = config.getfloat('value', minval=0., maxval=scale)
mcu_pwm.setup_static_pwm(value / scale)
######################################################################
# Servos
######################################################################
SERVO_MIN_TIME = 0.100
SERVO_SIGNAL_PERIOD = 0.020
class PrinterServo:
def __init__(self, printer, config):
self.mcu_servo = pins.setup_pin(printer, 'pwm', config.get('pin'))
self.mcu_servo.setup_max_duration(0.)
self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD)
self.min_width = config.getfloat(
'minimum_pulse_width', .001, above=0., below=SERVO_SIGNAL_PERIOD)
self.max_width = config.getfloat(
'maximum_pulse_width', .002
, above=self.min_width, below=SERVO_SIGNAL_PERIOD)
self.max_angle = config.getfloat('maximum_servo_angle', 180.)
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
self.last_value = self.last_value_time = 0.
def set_pwm(self, print_time, value):
if value == self.last_value:
return
print_time = max(self.last_value_time + SERVO_MIN_TIME, print_time)
self.mcu_servo.set_pwm(print_time, value)
self.last_value = value
self.last_value_time = print_time
# External commands
def set_angle(self, print_time, angle):
angle = max(0., min(self.max_angle, angle))
width = self.min_width + angle * self.angle_to_width
self.set_pwm(print_time, width * self.width_to_value)
def set_pulse_width(self, print_time, width):
width = max(self.min_width, min(self.max_width, width))
self.set_pwm(print_time, width * self.width_to_value)
def get_printer_servo(printer, name):
return printer.objects.get('servo ' + name)
######################################################################
# AD5206 digipot
######################################################################
class ad5206:
def __init__(self, printer, config):
enable_pin_params = pins.get_printer_pins(printer).parse_pin_desc(
config.get('enable_pin'))
self.mcu = enable_pin_params['chip']
self.pin = enable_pin_params['pin']
self.mcu.add_config_object(self)
scale = config.getfloat('scale', 1., above=0.)
self.channels = [None] * 6
for i in range(len(self.channels)):
val = config.getfloat('channel_%d' % (i+1,), None,
minval=0., maxval=scale)
if val is not None:
self.channels[i] = int(val * 256. / scale + .5)
def build_config(self):
for i, val in enumerate(self.channels):
if val is not None:
self.mcu.add_config_cmd(
"send_spi_message pin=%s msg=%02x%02x" % (self.pin, i, val))
######################################################################
# Replicape board
######################################################################
REPLICAPE_MAX_CURRENT = 3.84
REPLICAPE_SHIFT_REGISTER_BUS = 1
REPLICAPE_SHIFT_REGISTER_DEVICE = 1
REPLICAPE_PCA9685_BUS = 2
REPLICAPE_PCA9685_ADDRESS = 0x70
REPLICAPE_PCA9685_CYCLE_TIME = .001
class pca9685_pwm:
def __init__(self, replicape, channel, pin_params):
self._replicape = replicape
self._channel = channel
if pin_params['type'] not in ['digital_out', 'pwm']:
raise pins.error("Pin type not supported on replicape")
self._mcu = replicape.host_mcu
self._mcu.add_config_object(self)
self._bus = REPLICAPE_PCA9685_BUS
self._address = REPLICAPE_PCA9685_ADDRESS
self._cycle_time = REPLICAPE_PCA9685_CYCLE_TIME
self._max_duration = 2.
self._oid = None
self._invert = pin_params['invert']
self._shutdown_value = float(self._invert)
self._last_clock = 0
self._pwm_max = 0.
self._cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = None
self._static_value = None
def get_mcu(self):
return self._mcu
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
def setup_cycle_time(self, cycle_time):
pass
def setup_hard_pwm(self, hard_cycle_ticks):
if hard_cycle_ticks:
raise pins.error("pca9685 does not support hard_pwm parameter")
def setup_static_pwm(self, value):
if self._invert:
value = 1. - value
self._static_value = max(0., min(1., value))
def setup_shutdown_value(self, value):
if self._invert:
value = 1. - value
self._shutdown_value = max(0., min(1., value))
if self._shutdown_value:
self._replicape.note_enable_on_shutdown()
def build_config(self):
self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX")
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
if self._static_value is not None:
value = int(self._static_value * self._pwm_max + 0.5)
self._mcu.add_config_cmd(
"set_pca9685_out bus=%d addr=%d channel=%d"
" cycle_ticks=%d value=%d" % (
self._bus, self._address, self._channel,
cycle_ticks, value))
return
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_pca9685 oid=%d bus=%d addr=%d channel=%d cycle_ticks=%d"
" value=%d default_value=%d max_duration=%d" % (
self._oid, self._bus, self._address, self._channel, cycle_ticks,
self._invert * self._pwm_max,
self._shutdown_value * self._pwm_max,
self._mcu.seconds_to_clock(self._max_duration)))
self._set_cmd = self._mcu.lookup_command(
"schedule_pca9685_out oid=%c clock=%u value=%hu")
def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
if self._invert:
value = 1. - value
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
self._replicape.note_enable(print_time, self._channel, not not value)
msg = self._set_cmd.encode(self._oid, clock, value)
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
, cq=self._cmd_queue)
self._last_clock = clock
def set_digital(self, print_time, value):
if value:
self.set_pwm(print_time, 1.)
else:
self.set_pwm(print_time, 0.)
class ReplicapeDACEnable:
def __init__(self, replicape, channel, pin_params):
if pin_params['type'] != 'digital_out':
raise pins.error("Replicape virtual enable pin must be digital_out")
if pin_params['invert']:
raise pins.error("Replicape virtual enable pin can not be inverted")
self.mcu = replicape.host_mcu
self.value = replicape.stepper_dacs[channel]
self.pwm = pca9685_pwm(replicape, channel, pin_params)
self.last = 0
def get_mcu(self):
return self.mcu
def setup_max_duration(self, max_duration):
self.pwm.setup_max_duration(max_duration)
def set_digital(self, print_time, value):
if value:
self.pwm.set_pwm(print_time, self.value)
else:
self.pwm.set_pwm(print_time, 0.)
self.last = value
def get_last_setting(self):
return self.last
ReplicapeStepConfig = {
'disable': None,
'1': (1<<7)|(1<<5), '2': (1<<7)|(1<<5)|(1<<6), 'spread2': (1<<5),
'4': (1<<7)|(1<<5)|(1<<4), '16': (1<<7)|(1<<5)|(1<<6)|(1<<4),
'spread4': (1<<5)|(1<<4), 'spread16': (1<<7), 'stealth4': (1<<7)|(1<<6),
'stealth16': 0
}
class Replicape:
def __init__(self, printer, config):
pins.get_printer_pins(printer).register_chip('replicape', self)
revisions = {'B3': 'B3'}
config.getchoice('revision', revisions)
self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu'))
# Setup enable pin
self.mcu_enable = pins.setup_pin(
printer, 'digital_out', config.get('enable_pin', '!P9_41'))
self.mcu_enable.setup_max_duration(0.)
self.enabled_channels = {}
# Setup power pins
self.pins = {
"power_e": (pca9685_pwm, 5), "power_h": (pca9685_pwm, 3),
"power_hotbed": (pca9685_pwm, 4),
"power_fan0": (pca9685_pwm, 7), "power_fan1": (pca9685_pwm, 8),
"power_fan2": (pca9685_pwm, 9), "power_fan3": (pca9685_pwm, 10) }
# Setup stepper config
self.stepper_dacs = {}
shift_registers = [1] * 5
for port, name in enumerate('xyzeh'):
prefix = 'stepper_%s_' % (name,)
sc = config.getchoice(
prefix + 'microstep_mode', ReplicapeStepConfig, 'disable')
if sc is None:
continue
if config.getboolean(prefix + 'chopper_off_time_high', False):
sc |= 1<<3
if config.getboolean(prefix + 'chopper_hysteresis_high', False):
sc |= 1<<2
if config.getboolean(prefix + 'chopper_blank_time_high', True):
sc |= 1<<1
shift_registers[port] = sc
channel = port + 11
cur = config.getfloat(
prefix + 'current', above=0., maxval=REPLICAPE_MAX_CURRENT)
self.stepper_dacs[channel] = cur / REPLICAPE_MAX_CURRENT
self.pins[prefix + 'enable'] = (ReplicapeDACEnable, channel)
shift_registers.reverse()
self.host_mcu.add_config_cmd("send_spi bus=%d dev=%d msg=%s" % (
REPLICAPE_SHIFT_REGISTER_BUS, REPLICAPE_SHIFT_REGISTER_DEVICE,
"".join(["%02x" % (x,) for x in shift_registers])))
def note_enable_on_shutdown(self):
self.mcu_enable.setup_shutdown_value(1)
def note_enable(self, print_time, channel, is_enable):
if is_enable:
is_off = not self.enabled_channels
self.enabled_channels[channel] = 1
if is_off:
self.mcu_enable.set_digital(print_time, 1)
elif channel in self.enabled_channels:
del self.enabled_channels[channel]
if not self.enabled_channels:
self.mcu_enable.set_digital(print_time, 0)
def setup_pin(self, pin_params):
pin = pin_params['pin']
if pin not in self.pins:
raise pins.error("Unknown replicape pin %s" % (pin,))
pclass, channel = self.pins[pin]
return pclass(self, channel, pin_params)
######################################################################
# Setup
######################################################################
def add_printer_objects(printer, config):
if config.has_section('replicape'):
printer.add_object('replicape', Replicape(
printer, config.getsection('replicape')))
for s in config.get_prefix_sections('static_digital_output '):
printer.add_object(s.section, PrinterStaticDigitalOut(printer, s))
for s in config.get_prefix_sections('static_pwm_output '):
printer.add_object(s.section, PrinterStaticPWM(printer, s))
for s in config.get_prefix_sections('servo '):
printer.add_object(s.section, PrinterServo(printer, s))
for s in config.get_prefix_sections('ad5206 '):
printer.add_object(s.section, ad5206(printer, s))

View File

@@ -7,7 +7,7 @@ import logging, threading, math
COMM_TIMEOUT = 3.5
RTT_AGE = .000010 / (60. * 60.)
DECAY = 1. / (2. * 60.)
DECAY = 1. / 30.
TRANSMIT_EXTRA = .001
class ClockSync:
@@ -29,20 +29,19 @@ class ClockSync:
self.last_prediction_time = 0.
def connect(self, serial):
self.serial = serial
msgparser = serial.msgparser
self.mcu_freq = msgparser.get_constant_float('CLOCK_FREQ')
self.mcu_freq = serial.msgparser.get_constant_float('CLOCK_FREQ')
# Load initial clock and frequency
uptime_msg = msgparser.create_command('get_uptime')
params = serial.send_with_response(uptime_msg, 'uptime')
get_uptime_cmd = serial.lookup_command('get_uptime')
params = get_uptime_cmd.send_with_response(response='uptime')
self.last_clock = (params['high'] << 32) | params['clock']
self.clock_avg = self.last_clock
self.time_avg = params['#sent_time']
self.clock_est = (self.time_avg, self.clock_avg, self.mcu_freq)
self.prediction_variance = (.001 * self.mcu_freq)**2
# Enable periodic get_status timer
self.status_cmd = msgparser.create_command('get_status')
self.status_cmd = serial.lookup_command('get_status')
for i in range(8):
params = serial.send_with_response(self.status_cmd, 'status')
params = self.status_cmd.send_with_response(response='status')
self._handle_status(params)
self.reactor.pause(0.100)
serial.register_callback(self._handle_status, 'status')
@@ -57,7 +56,7 @@ class ClockSync:
serial.set_clock_est(freq, self.reactor.monotonic(), 0)
# MCU clock querying (status callback invoked from background thread)
def _status_event(self, eventtime):
self.serial.send(self.status_cmd)
self.status_cmd.send()
return eventtime + 1.0
def _handle_status(self, params):
# Extend clock to 64bit
@@ -113,7 +112,7 @@ class ClockSync:
pred_stddev = math.sqrt(self.prediction_variance)
self.serial.set_clock_est(new_freq, self.time_avg + TRANSMIT_EXTRA,
int(self.clock_avg - 3. * pred_stddev))
self.clock_est = (self.time_avg - self.min_half_rtt,
self.clock_est = (self.time_avg + self.min_half_rtt,
self.clock_avg, new_freq)
#logging.debug("regr %.3f: freq=%.3f d=%d(%.3f)",
# sent_time, new_freq, clock - exp_clock, pred_stddev)
@@ -165,6 +164,7 @@ class SecondarySync(ClockSync):
ClockSync.__init__(self, reactor)
self.main_sync = main_sync
self.clock_adj = (0., 1.)
self.last_sync_time = 0.
def connect(self, serial):
ClockSync.connect(self, serial)
self.clock_adj = (0., self.mcu_freq)
@@ -195,18 +195,25 @@ class SecondarySync(ClockSync):
adjusted_offset, adjusted_freq = self.clock_adj
return "%s adj=%d" % (ClockSync.stats(self, eventtime), adjusted_freq)
def calibrate_clock(self, print_time, eventtime):
# Calculate: est_print_time = main_sync.estimatated_print_time()
ser_time, ser_clock, ser_freq = self.main_sync.clock_est
main_mcu_freq = self.main_sync.mcu_freq
main_clock = (eventtime - ser_time) * ser_freq + ser_clock
print_time = max(print_time, main_clock / main_mcu_freq)
main_sync_clock = (print_time + 4.) * main_mcu_freq
sync_time = ser_time + (main_sync_clock - ser_clock) / ser_freq
print_clock = self.print_time_to_clock(print_time)
sync_clock = self.get_clock(sync_time)
adjusted_freq = .25 * (sync_clock - print_clock)
adjusted_offset = print_time - print_clock / adjusted_freq
est_main_clock = (eventtime - ser_time) * ser_freq + ser_clock
est_print_time = est_main_clock / main_mcu_freq
# Determine sync1_print_time and sync2_print_time
sync1_print_time = max(print_time, est_print_time)
sync2_print_time = max(sync1_print_time + 4., self.last_sync_time,
print_time + 2.5 * (print_time - est_print_time))
# Calc sync2_sys_time (inverse of main_sync.estimatated_print_time)
sync2_main_clock = sync2_print_time * main_mcu_freq
sync2_sys_time = ser_time + (sync2_main_clock - ser_clock) / ser_freq
# Adjust freq so estimated print_time will match at sync2_print_time
sync1_clock = self.print_time_to_clock(sync1_print_time)
sync2_clock = self.get_clock(sync2_sys_time)
adjusted_freq = ((sync2_clock - sync1_clock)
/ (sync2_print_time - sync1_print_time))
adjusted_offset = sync1_print_time - sync1_clock / adjusted_freq
# Apply new values
self.clock_adj = (adjusted_offset, adjusted_freq)
self.last_sync_time = sync2_print_time
return self.clock_adj

View File

@@ -55,8 +55,8 @@ class KeyboardReader:
self.clocksync.connect(self.ser)
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)
mcu_type = self.ser.msgparser.get_constant('MCU')
self.pins = pins.PinResolver(mcu_type, validate_aliases=False)
self.reactor.unregister_timer(self.connect_timer)
self.output("="*20 + " connected " + "="*20)
return self.reactor.NEVER
@@ -71,8 +71,7 @@ class KeyboardReader:
self.eval_globals['freq'] = self.mcu_freq
self.eval_globals['clock'] = self.clocksync.get_clock(eventtime)
def command_PINS(self, parts):
mcu = self.ser.msgparser.get_constant('MCU')
self.pins = pins.get_pin_map(mcu, parts[1])
self.pins.update_aliases(parts[1])
def command_SET(self, parts):
val = parts[2]
try:
@@ -87,11 +86,10 @@ class KeyboardReader:
self.output("Error: %s" % (str(e),))
return
try:
msg = self.ser.msgparser.create_command(' '.join(parts[2:]))
self.ser.send(' '.join(parts[2:]), minclock=val)
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return
self.ser.send(msg, minclock=val)
def command_FLOOD(self, parts):
try:
count = int(parts[1])
@@ -99,18 +97,18 @@ class KeyboardReader:
except ValueError as e:
self.output("Error: %s" % (str(e),))
return
try:
msg = self.ser.msgparser.create_command(' '.join(parts[3:]))
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return
msg = ' '.join(parts[3:])
delay_clock = int(delay * self.mcu_freq)
msg_clock = int(self.clocksync.get_clock(self.reactor.monotonic())
+ self.mcu_freq * .200)
try:
for i in range(count):
next_clock = msg_clock + delay_clock
self.ser.send(msg, minclock=msg_clock, reqclock=next_clock)
msg_clock = next_clock
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return
def command_SUPPRESS(self, parts):
oid = None
try:
@@ -156,7 +154,7 @@ class KeyboardReader:
self.output("Eval: %s" % (line,))
if self.pins is not None:
try:
line = pins.update_command(line, self.pins).strip()
line = self.pins.update_command(line).strip()
except:
self.output("Unable to map pin: %s" % (line,))
return None
@@ -165,12 +163,7 @@ class KeyboardReader:
if parts[0] in self.local_commands:
self.local_commands[parts[0]](parts)
return None
try:
msg = self.ser.msgparser.create_command(line)
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return None
return msg
return line
def process_kbd(self, eventtime):
self.data += os.read(self.fd, 4096)
@@ -185,7 +178,11 @@ class KeyboardReader:
msg = self.translate(line.strip(), eventtime)
if msg is None:
continue
try:
self.ser.send(msg)
except msgproto.error as e:
self.output("Error: %s" % (str(e),))
return None
self.data = kbdlines[-1]
def main():

View File

@@ -10,9 +10,13 @@ StepList = (0, 1, 2)
class CoreXYKinematics:
def __init__(self, toolhead, printer, config):
self.steppers = [stepper.PrinterHomingStepper(
printer, config.getsection('stepper_' + n), n)
for n in ['x', 'y', 'z']]
self.steppers = [
stepper.PrinterHomingStepper(
printer, config.getsection('stepper_x')),
stepper.PrinterHomingStepper(
printer, config.getsection('stepper_y')),
stepper.LookupMultiHomingStepper(
printer, config.getsection('stepper_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)
max_velocity, max_accel = toolhead.get_max_velocity()
@@ -29,15 +33,24 @@ class CoreXYKinematics:
self.steppers[1].set_max_jerk(max_xy_halt_velocity, max_accel)
self.steppers[2].set_max_jerk(
min(max_halt_velocity, self.max_z_velocity), self.max_z_accel)
def set_position(self, newpos):
def get_steppers(self, flags=""):
if flags == "Z":
return [self.steppers[2]]
return list(self.steppers)
def get_position(self):
pos = [s.mcu_stepper.get_commanded_position() for s in self.steppers]
return [0.5 * (pos[0] + pos[1]), 0.5 * (pos[0] - pos[1]), pos[2]]
def set_position(self, newpos, homing_axes):
pos = (newpos[0] + newpos[1], newpos[0] - newpos[1], newpos[2])
for i in StepList:
self.steppers[i].mcu_stepper.set_position(pos[i])
s = self.steppers[i]
s.set_position(pos[i])
if i in homing_axes:
self.limits[i] = (s.position_min, s.position_max)
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*(
@@ -50,25 +63,25 @@ class CoreXYKinematics:
rpos = s.position_endstop + s.homing_retract_dist
r2pos = rpos + s.homing_retract_dist
# Initial homing
homing_speed = s.get_homing_speed()
homing_speed = s.homing_speed
if axis == 2:
homing_speed = min(homing_speed, self.max_z_velocity)
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], homing_speed)
homing_state.home(coord, homepos, s.get_endstops(), homing_speed)
# Retract
coord[axis] = rpos
homing_state.retract(list(coord), homing_speed)
homing_state.retract(coord, homing_speed)
# Home again
coord[axis] = r2pos
homing_state.home(
list(coord), homepos, [s], homing_speed/2.0, second_home=True)
homing_state.home(coord, homepos, s.get_endstops(),
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 query_endstops(self, print_time, query_flags):
return homing.query_endstops(print_time, query_flags, self.steppers)
def motor_off(self, print_time):
self.limits = [(1.0, -1.0)] * 3
for stepper in self.steppers:
@@ -122,7 +135,7 @@ class CoreXYKinematics:
axis_d = axes_d[i]
if not axis_d:
continue
mcu_stepper = self.steppers[i].mcu_stepper
step_const = self.steppers[i].step_const
move_time = print_time
start_pos = move_start_pos[i]
axis_r = abs(axis_d) / move.move_d
@@ -132,19 +145,17 @@ class CoreXYKinematics:
# Acceleration steps
if move.accel_r:
accel_d = move.accel_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, accel_d, move.start_v * axis_r, accel)
step_const(move_time, start_pos, accel_d,
move.start_v * axis_r, accel)
start_pos += accel_d
move_time += move.accel_t
# Cruising steps
if move.cruise_r:
cruise_d = move.cruise_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, cruise_d, cruise_v, 0.)
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
move_time += move.cruise_t
# Deceleration steps
if move.decel_r:
decel_d = move.decel_r * axis_d
mcu_stepper.step_const(
move_time, start_pos, decel_d, cruise_v, -accel)
step_const(move_time, start_pos, decel_d, cruise_v, -accel)

View File

@@ -13,17 +13,30 @@ SLOW_RATIO = 3.
class DeltaKinematics:
def __init__(self, toolhead, printer, config):
self.steppers = [stepper.PrinterHomingStepper(
printer, config.getsection('stepper_' + n), n)
stepper_configs = [config.getsection('stepper_' + n)
for n in ['a', 'b', 'c']]
stepper_a = stepper.PrinterHomingStepper(printer, stepper_configs[0])
stepper_b = stepper.PrinterHomingStepper(
printer, stepper_configs[1],
default_position=stepper_a.position_endstop)
stepper_c = stepper.PrinterHomingStepper(
printer, stepper_configs[2],
default_position=stepper_a.position_endstop)
self.steppers = [stepper_a, stepper_b, stepper_c]
self.need_motor_enable = self.need_home = True
radius = config.getfloat('delta_radius', above=0.)
arm_length = config.getfloat('delta_arm_length', above=radius)
self.arm_length2 = arm_length**2
self.radius = radius = config.getfloat('delta_radius', above=0.)
arm_length_a = stepper_configs[0].getfloat('arm_length', above=radius)
self.arm_lengths = arm_lengths = [
sconfig.getfloat('arm_length', arm_length_a, above=radius)
for sconfig in stepper_configs]
self.arm2 = [arm**2 for arm in arm_lengths]
self.endstops = [s.position_endstop + math.sqrt(arm2 - radius**2)
for s, arm2 in zip(self.steppers, self.arm2)]
self.limit_xy2 = -1.
tower_height_at_zeros = math.sqrt(self.arm_length2 - radius**2)
self.max_z = min([s.position_endstop for s in self.steppers])
self.limit_z = self.max_z - (arm_length - tower_height_at_zeros)
self.min_z = config.getfloat('minimum_z_position', 0, maxval=self.max_z)
self.limit_z = min([ep - arm
for ep, arm in zip(self.endstops, arm_lengths)])
logging.info(
"Delta max build height %.2fmm (radius tapered above %.2fmm)" % (
self.max_z, self.limit_z))
@@ -36,95 +49,69 @@ class DeltaKinematics:
for s in self.steppers:
s.set_max_jerk(max_halt_velocity, self.max_accel)
# Determine tower locations in cartesian space
angles = [config.getsection('stepper_a').getfloat('angle', 210.),
config.getsection('stepper_b').getfloat('angle', 330.),
config.getsection('stepper_c').getfloat('angle', 90.)]
self.angles = [sconfig.getfloat('angle', angle)
for sconfig, angle in zip(stepper_configs,
[210., 330., 90.])]
self.towers = [(math.cos(math.radians(angle)) * radius,
math.sin(math.radians(angle)) * radius)
for angle in angles]
for angle in self.angles]
# 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
min_arm_length = min(arm_lengths)
def ratio_to_dist(ratio):
return (ratio * math.sqrt(self.arm_length2 / (ratio**2 + 1.)
return (ratio * math.sqrt(min_arm_length**2 / (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,
self.max_xy2 = min(radius, min_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.])
self.set_position([0., 0., 0.], ())
def get_steppers(self, flags=""):
return list(self.steppers)
def _cartesian_to_actuator(self, coord):
return [math.sqrt(self.arm_length2
- (self.towers[i][0] - coord[0])**2
return [math.sqrt(self.arm2[i] - (self.towers[i][0] - coord[0])**2
- (self.towers[i][1] - coord[1])**2) + coord[2]
for i in StepList]
def _actuator_to_cartesian(self, pos):
# Based on code from Smoothieware
tower1 = list(self.towers[0]) + [pos[0]]
tower2 = list(self.towers[1]) + [pos[1]]
tower3 = list(self.towers[2]) + [pos[2]]
s12 = matrix_sub(tower1, tower2)
s23 = matrix_sub(tower2, tower3)
s13 = matrix_sub(tower1, tower3)
normal = matrix_cross(s12, s23)
magsq_s12 = matrix_magsq(s12)
magsq_s23 = matrix_magsq(s23)
magsq_s13 = matrix_magsq(s13)
inv_nmag_sq = 1.0 / matrix_magsq(normal)
q = 0.5 * inv_nmag_sq
a = q * magsq_s23 * matrix_dot(s12, s13)
b = -q * magsq_s13 * matrix_dot(s12, s23) # negate because we use s12 instead of s21
c = q * magsq_s12 * matrix_dot(s13, s23)
circumcenter = [tower1[0] * a + tower2[0] * b + tower3[0] * c,
tower1[1] * a + tower2[1] * b + tower3[1] * c,
tower1[2] * a + tower2[2] * b + tower3[2] * c]
r_sq = 0.5 * q * magsq_s12 * magsq_s23 * magsq_s13
dist = math.sqrt(inv_nmag_sq * (self.arm_length2 - r_sq))
return matrix_sub(circumcenter, matrix_mul(normal, dist))
def set_position(self, newpos):
return actuator_to_cartesian(self.towers, self.arm2, pos)
def get_position(self):
spos = [s.mcu_stepper.get_commanded_position() for s in self.steppers]
return self._actuator_to_cartesian(spos)
def set_position(self, newpos, homing_axes):
pos = self._cartesian_to_actuator(newpos)
for i in StepList:
self.steppers[i].mcu_stepper.set_position(pos[i])
self.steppers[i].set_position(pos[i])
self.limit_xy2 = -1.
if tuple(homing_axes) == StepList:
self.need_home = False
def home(self, homing_state):
# All axes are homed simultaneously
homing_state.set_axes([0, 1, 2])
endstops = [es for s in self.steppers for es in s.get_endstops()]
s = self.steppers[0] # Assume homing speed same for all steppers
self.need_home = False
# Initial homing
homing_speed = s.get_homing_speed()
homing_speed = min(s.homing_speed, self.max_z_velocity)
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, homing_speed)
coord[2] = -1.5 * math.sqrt(max(self.arm2)-self.max_xy2)
homing_state.home(coord, homepos, endstops, homing_speed)
# Retract
coord[2] = homepos[2] - s.homing_retract_dist
homing_state.retract(list(coord), homing_speed)
homing_state.retract(coord, homing_speed)
# Home again
coord[2] -= s.homing_retract_dist
homing_state.home(list(coord), homepos, self.steppers
, homing_speed/2.0, second_home=True)
homing_state.home(coord, homepos, endstops,
homing_speed/2.0, second_home=True)
# Set final homed position
spos = self._cartesian_to_actuator(homepos)
spos = [spos[i] + self.steppers[i].position_endstop - self.max_z
+ self.steppers[i].get_homed_offset()
for i in StepList]
spos = [ep + s.get_homed_offset()
for ep, s in zip(self.endstops, self.steppers)]
homing_state.set_homed_position(self._actuator_to_cartesian(spos))
def query_endstops(self, print_time, query_flags):
return homing.query_endstops(print_time, query_flags, self.steppers)
def motor_off(self, print_time):
self.limit_xy2 = -1.
for stepper in self.steppers:
@@ -145,7 +132,7 @@ class DeltaKinematics:
limit_xy2 = self.max_xy2
if end_pos[2] > self.limit_z:
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:
if xy2 > limit_xy2 or end_pos[2] < self.min_z or end_pos[2] > self.max_z:
raise homing.EndstopMoveError(end_pos)
if move.axes_d[2]:
move.limit_speed(self.max_z_velocity, move.accel)
@@ -197,30 +184,42 @@ class DeltaKinematics:
towery_d = self.towers[i][1] - origy
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_arm_d = math.sqrt(self.arm2[i] - tangentxy_d2)
vt_startz = origz
# Generate steps
mcu_stepper = self.steppers[i].mcu_stepper
step_delta = self.steppers[i].step_delta
move_time = print_time
if accel_d:
mcu_stepper.step_delta(
move_time, accel_d, move.start_v, accel,
step_delta(move_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
move_time += move.accel_t
if cruise_d:
mcu_stepper.step_delta(
move_time, cruise_d, cruise_v, 0.,
step_delta(move_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
move_time += move.cruise_t
if decel_d:
mcu_stepper.step_delta(
move_time, decel_d, cruise_v, -accel,
step_delta(move_time, decel_d, cruise_v, -accel,
vt_startz, vt_startxy_d, vt_arm_d, movez_r)
# Helper functions for DELTA_CALIBRATE script
def get_stable_position(self):
return [int((ep - s.mcu_stepper.get_commanded_position())
/ s.mcu_stepper.get_step_dist() + .5)
* s.mcu_stepper.get_step_dist()
for ep, s in zip(self.endstops, self.steppers)]
def get_calibrate_params(self):
return {
'endstop_a': self.steppers[0].position_endstop,
'endstop_b': self.steppers[1].position_endstop,
'endstop_c': self.steppers[2].position_endstop,
'angle_a': self.angles[0], 'angle_b': self.angles[1],
'angle_c': self.angles[2], 'radius': self.radius,
'arm_a': self.arm_lengths[0], 'arm_b': self.arm_lengths[1],
'arm_c': self.arm_lengths[2] }
######################################################################
@@ -238,8 +237,49 @@ def matrix_dot(m1, m2):
def matrix_magsq(m1):
return m1[0]**2 + m1[1]**2 + m1[2]**2
def matrix_add(m1, m2):
return [m1[0] + m2[0], m1[1] + m2[1], m1[2] + m2[2]]
def matrix_sub(m1, m2):
return [m1[0] - m2[0], m1[1] - m2[1], m1[2] - m2[2]]
def matrix_mul(m1, s):
return [m1[0]*s, m1[1]*s, m1[2]*s]
def actuator_to_cartesian(towers, arm2, pos):
# Find nozzle position using trilateration (see wikipedia)
carriage1 = list(towers[0]) + [pos[0]]
carriage2 = list(towers[1]) + [pos[1]]
carriage3 = list(towers[2]) + [pos[2]]
s21 = matrix_sub(carriage2, carriage1)
s31 = matrix_sub(carriage3, carriage1)
d = math.sqrt(matrix_magsq(s21))
ex = matrix_mul(s21, 1. / d)
i = matrix_dot(ex, s31)
vect_ey = matrix_sub(s31, matrix_mul(ex, i))
ey = matrix_mul(vect_ey, 1. / math.sqrt(matrix_magsq(vect_ey)))
ez = matrix_cross(ex, ey)
j = matrix_dot(ey, s31)
x = (arm2[0] - arm2[1] + d**2) / (2. * d)
y = (arm2[0] - arm2[2] - x**2 + (x-i)**2 + j**2) / (2. * j)
z = -math.sqrt(arm2[0] - x**2 - y**2)
ex_x = matrix_mul(ex, x)
ey_y = matrix_mul(ey, y)
ez_z = matrix_mul(ez, z)
return matrix_add(carriage1, matrix_add(ex_x, matrix_add(ey_y, ez_z)))
def get_position_from_stable(spos, params):
angles = [params['angle_a'], params['angle_b'], params['angle_c']]
radius = params['radius']
radius2 = radius**2
towers = [(math.cos(angle) * radius, math.sin(angle) * radius)
for angle in map(math.radians, angles)]
arm2 = [a**2 for a in [params['arm_a'], params['arm_b'], params['arm_c']]]
endstops = [params['endstop_a'], params['endstop_b'], params['endstop_c']]
pos = [es + math.sqrt(a2 - radius2) - p
for es, a2, p in zip(endstops, arm2, spos)]
return actuator_to_cartesian(towers, arm2, pos)

View File

@@ -0,0 +1,5 @@
# Package definition for the extras directory
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.

32
klippy/extras/ad5206.py Normal file
View File

@@ -0,0 +1,32 @@
# AD5206 digipot code
#
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins
class ad5206:
def __init__(self, config):
printer = config.get_printer()
enable_pin_params = pins.get_printer_pins(printer).lookup_pin(
'digital_out', config.get('enable_pin'))
if enable_pin_params['invert']:
raise pins.error("ad5206 can not invert pin")
self.mcu = enable_pin_params['chip']
self.pin = enable_pin_params['pin']
self.mcu.add_config_object(self)
scale = config.getfloat('scale', 1., above=0.)
self.channels = [None] * 6
for i in range(len(self.channels)):
val = config.getfloat('channel_%d' % (i+1,), None,
minval=0., maxval=scale)
if val is not None:
self.channels[i] = int(val * 256. / scale + .5)
def build_config(self):
for i, val in enumerate(self.channels):
if val is not None:
self.mcu.add_config_cmd(
"send_spi_message pin=%s msg=%02x%02x" % (self.pin, i, val))
def load_config_prefix(config):
return ad5206(config)

113
klippy/extras/bed_tilt.py Normal file
View File

@@ -0,0 +1,113 @@
# Bed tilt compensation
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import probe, mathutil
class BedTilt:
def __init__(self, config):
self.printer = config.get_printer()
self.x_adjust = config.getfloat('x_adjust', 0.)
self.y_adjust = config.getfloat('y_adjust', 0.)
if config.get('points', None) is not None:
BedTiltCalibrate(config, self)
self.toolhead = None
gcode = self.printer.lookup_object('gcode')
gcode.set_move_transform(self)
def printer_state(self, state):
if state == 'connect':
self.toolhead = self.printer.lookup_object('toolhead')
def get_position(self):
x, y, z, e = self.toolhead.get_position()
return [x, y, z - x*self.x_adjust - y*self.y_adjust, e]
def move(self, newpos, speed):
x, y, z, e = newpos
self.toolhead.move([x, y, z + x*self.x_adjust + y*self.y_adjust, e],
speed)
# Helper script to calibrate the bed tilt
class BedTiltCalibrate:
def __init__(self, config, bedtilt):
self.bedtilt = bedtilt
self.printer = config.get_printer()
points = config.get('points').split('\n')
try:
points = [line.split(',', 1) for line in points if line.strip()]
self.points = [(float(p[0].strip()), float(p[1].strip()))
for p in points]
except:
raise config.error("Unable to parse bed tilt points")
if len(self.points) < 3:
raise config.error("Need at least 3 points for bed_tilt_calibrate")
self.speed = config.getfloat('speed', 50., above=0.)
self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
self.z_position_endstop = None
if config.has_section('stepper_z'):
zconfig = config.getsection('stepper_z')
self.z_position_endstop = zconfig.getfloat('position_endstop', None)
self.manual_probe = config.getboolean('manual_probe', None)
if self.manual_probe is None:
self.manual_probe = not config.has_section('probe')
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'BED_TILT_CALIBRATE', self.cmd_BED_TILT_CALIBRATE,
desc=self.cmd_BED_TILT_CALIBRATE_help)
cmd_BED_TILT_CALIBRATE_help = "Bed tilt calibration script"
def cmd_BED_TILT_CALIBRATE(self, params):
self.gcode.run_script("G28")
probe.ProbePointsHelper(
self.printer, self.points, self.horizontal_move_z,
self.speed, self.manual_probe, self)
def get_position(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
return kin.get_position()
def finalize(self, z_offset, positions):
logging.info("Calculating bed_tilt with: %s", positions)
params = { 'x_adjust': self.bedtilt.x_adjust,
'y_adjust': self.bedtilt.y_adjust,
'z_adjust': z_offset }
logging.info("Initial bed_tilt parameters: %s", params)
def adjusted_height(pos, params):
x, y, z = pos
return (z - x*params['x_adjust'] - y*params['y_adjust']
- params['z_adjust'])
def errorfunc(params):
total_error = 0.
for pos in positions:
total_error += adjusted_height(pos, params)**2
return total_error
new_params = mathutil.coordinate_descent(
params.keys(), params, errorfunc)
logging.info("Calculated bed_tilt parameters: %s", new_params)
for pos in positions:
logging.info("orig: %s new: %s", adjusted_height(pos, params),
adjusted_height(pos, new_params))
z_diff = new_params['z_adjust'] - z_offset
if self.z_position_endstop is not None:
# Cartesian style robot
z_extra = ""
probe = self.printer.lookup_object('probe', None)
if probe is not None:
last_home_position = probe.last_home_position()
if last_home_position is not None:
# Using z_virtual_endstop
home_x, home_y = last_home_position[:2]
z_diff -= home_x * new_params['x_adjust']
z_diff -= home_y * new_params['y_adjust']
z_extra = " (when Z homing at %.3f,%.3f)" % (home_x, home_y)
z_adjust = "stepper_z position_endstop: %.6f%s\n" % (
self.z_position_endstop - z_diff, z_extra)
else:
# Delta (or other) style robot
z_adjust = "Add %.6f to endstop position\n" % (-z_diff,)
msg = "%sx_adjust: %.6f y_adjust: %.6f" % (
z_adjust, new_params['x_adjust'], new_params['y_adjust'])
logging.info("bed_tilt_calibrate: %s", msg)
self.gcode.respond_info(
"%s\nTo use these parameters, update the printer config file with\n"
"the above and then issue a RESTART command" % (msg,))
def load_config(config):
return BedTilt(config)

View File

@@ -0,0 +1,73 @@
# Delta calibration support
#
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
import probe, delta, mathutil
class DeltaCalibrate:
def __init__(self, config):
self.printer = config.get_printer()
if config.getsection('printer').get('kinematics') != 'delta':
raise config.error("Delta calibrate is only for delta printers")
self.radius = config.getfloat('radius', above=0.)
self.speed = config.getfloat('speed', 50., above=0.)
self.horizontal_move_z = config.getfloat('horizontal_move_z', 5.)
self.manual_probe = config.getboolean('manual_probe', None)
if self.manual_probe is None:
self.manual_probe = not config.has_section('probe')
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'DELTA_CALIBRATE', self.cmd_DELTA_CALIBRATE,
desc=self.cmd_DELTA_CALIBRATE_help)
cmd_DELTA_CALIBRATE_help = "Delta calibration script"
def cmd_DELTA_CALIBRATE(self, params):
# Setup probe points
points = [(0., 0.)]
scatter = [.95, .90, .85, .70, .75, .80]
for i in range(6):
r = math.radians(90. + 60. * i)
dist = self.radius * scatter[i]
points.append((math.cos(r) * dist, math.sin(r) * dist))
# Probe them
self.gcode.run_script("G28")
probe.ProbePointsHelper(self.printer, points, self.horizontal_move_z,
self.speed, self.manual_probe, self)
def get_position(self):
kin = self.printer.lookup_object('toolhead').get_kinematics()
return kin.get_stable_position()
def finalize(self, z_offset, positions):
kin = self.printer.lookup_object('toolhead').get_kinematics()
logging.info("Calculating delta_calibrate with: %s", positions)
params = kin.get_calibrate_params()
logging.info("Initial delta_calibrate parameters: %s", params)
adj_params = ('endstop_a', 'endstop_b', 'endstop_c', 'radius',
'angle_a', 'angle_b')
def delta_errorfunc(params):
total_error = 0.
for spos in positions:
x, y, z = delta.get_position_from_stable(spos, params)
total_error += (z - z_offset)**2
return total_error
new_params = mathutil.coordinate_descent(
adj_params, params, delta_errorfunc)
logging.info("Calculated delta_calibrate parameters: %s", new_params)
for spos in positions:
logging.info("orig: %s new: %s",
delta.get_position_from_stable(spos, params),
delta.get_position_from_stable(spos, new_params))
self.gcode.respond_info(
"stepper_a: position_endstop: %.6f angle: %.6f\n"
"stepper_b: position_endstop: %.6f angle: %.6f\n"
"stepper_c: position_endstop: %.6f angle: %.6f\n"
"radius: %.6f\n"
"To use these parameters, update the printer config file with\n"
"the above and then issue a RESTART command" % (
new_params['endstop_a'], new_params['angle_a'],
new_params['endstop_b'], new_params['angle_b'],
new_params['endstop_c'], new_params['angle_c'],
new_params['radius']))
def load_config(config):
return DeltaCalibrate(config)

602
klippy/extras/display.py Normal file
View File

@@ -0,0 +1,602 @@
# Basic LCD display support
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2018 Aleph Objects, Inc <marcio@alephobjects.com>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
######################################################################
# HD44780 (20x4 text) lcd chip
######################################################################
HD44780_DELAY = .000037
class HD44780:
char_right_arrow = '\x7e'
char_thermometer = '\x00'
char_heater_bed = '\x01'
char_speed_factor = '\x02'
char_clock = '\x03'
char_degrees = '\x04'
def __init__(self, config):
self.printer = config.get_printer()
# pin config
ppins = self.printer.lookup_object('pins')
pins = [ppins.lookup_pin('digital_out', config.get(name + '_pin'))
for name in ['rs', 'e', 'd4', 'd5', 'd6', 'd7']]
mcu = None
for pin_params in pins:
if mcu is not None and pin_params['chip'] != mcu:
raise ppins.error("hd44780 all pins must be on same mcu")
mcu = pin_params['chip']
if pin_params['invert']:
raise ppins.error("hd44780 can not invert pin")
self.pins = [pin_params['pin'] for pin_params in pins]
self.mcu = mcu
self.oid = self.mcu.create_oid()
self.mcu.add_config_object(self)
self.send_data_cmd = self.send_cmds_cmd = None
# framebuffers
self.text_framebuffer = (bytearray(' '*80), bytearray('~'*80), 0x80)
self.glyph_framebuffer = (bytearray(64), bytearray('~'*64), 0x40)
self.framebuffers = [self.text_framebuffer, self.glyph_framebuffer]
def build_config(self):
self.mcu.add_config_cmd(
"config_hd44780 oid=%d rs_pin=%s e_pin=%s"
" d4_pin=%s d5_pin=%s d6_pin=%s d7_pin=%s delay_ticks=%d" % (
self.oid, self.pins[0], self.pins[1],
self.pins[2], self.pins[3], self.pins[4], self.pins[5],
self.mcu.seconds_to_clock(HD44780_DELAY)))
cmd_queue = self.mcu.alloc_command_queue()
self.send_cmds_cmd = self.mcu.lookup_command(
"hd44780_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
self.send_data_cmd = self.mcu.lookup_command(
"hd44780_send_data oid=%c data=%*s", cq=cmd_queue)
def send(self, cmds, is_data=False):
cmd_type = self.send_cmds_cmd
if is_data:
cmd_type = self.send_data_cmd
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
#logging.debug("hd44780 %d %s", is_data, repr(cmds))
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_id in self.framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data, old_data))
if nd != od]
# Batch together changes that are close to each other
for i in range(len(diffs)-2, -1, -1):
pos, count = diffs[i]
nextpos, nextcount = diffs[i+1]
if pos + 4 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
del diffs[i+1]
# Transmit changes
for pos, count in diffs:
chip_pos = pos
if fb_id == 0x80 and pos >= 40:
chip_pos += 0x40 - 40
self.send([fb_id + chip_pos])
self.send(new_data[pos:pos+count], is_data=True)
old_data[:] = new_data
def init(self):
curtime = self.printer.get_reactor().monotonic()
print_time = self.mcu.estimated_print_time(curtime)
# Program 4bit / 2-line mode and then issue 0x02 "Home" command
init = [[0x33], [0x33], [0x33, 0x22, 0x28, 0x02]]
# Reset (set positive direction ; enable display and hide cursor)
init.append([0x06, 0x0c])
for i, cmds in enumerate(init):
minclock = self.mcu.print_time_to_clock(print_time + i * .100)
self.send_cmds_cmd.send([self.oid, cmds], minclock=minclock)
# Add custom fonts
self.glyph_framebuffer[0][:len(HD44780_chars)] = HD44780_chars
for i in range(len(self.glyph_framebuffer[0])):
self.glyph_framebuffer[1][i] = self.glyph_framebuffer[0][i] ^ 1
self.flush()
def write_text(self, x, y, data):
if x + len(data) > 20:
data = data[:20 - min(x, 20)]
pos = [0, 40, 20, 60][y] + x
self.text_framebuffer[0][pos:pos+len(data)] = data
def clear(self):
self.text_framebuffer[0][:] = ' '*80
HD44780_chars = [
# Thermometer
0b00100,
0b01010,
0b01010,
0b01010,
0b01010,
0b10001,
0b10001,
0b01110,
# Heated bed
0b00000,
0b11111,
0b10101,
0b10001,
0b10101,
0b11111,
0b00000,
0b00000,
# Speed factor
0b11100,
0b10000,
0b11000,
0b10111,
0b00101,
0b00110,
0b00101,
0b00000,
# Clock
0b00000,
0b01110,
0b10011,
0b10101,
0b10001,
0b01110,
0b00000,
0b00000,
# Degrees
0b01100,
0b10010,
0b10010,
0b01100,
0b00000,
0b00000,
0b00000,
0b00000,
]
######################################################################
# ST7920 (128x64 graphics) lcd chip
######################################################################
ST7920_DELAY = .000020 # Spec says 72us, but faster is possible in practice
class ST7920:
char_right_arrow = '\x1a'
def __init__(self, config):
printer = config.get_printer()
# pin config
ppins = printer.lookup_object('pins')
pins = [ppins.lookup_pin('digital_out', config.get(name + '_pin'))
for name in ['cs', 'sclk', 'sid']]
mcu = None
for pin_params in pins:
if mcu is not None and pin_params['chip'] != mcu:
raise ppins.error("st7920 all pins must be on same mcu")
mcu = pin_params['chip']
if pin_params['invert']:
raise ppins.error("st7920 can not invert pin")
self.pins = [pin_params['pin'] for pin_params in pins]
self.mcu = mcu
self.oid = self.mcu.create_oid()
self.mcu.add_config_object(self)
self.send_data_cmd = self.send_cmds_cmd = None
self.is_extended = False
# framebuffers
self.text_framebuffer = (bytearray(' '*64), bytearray('~'*64), 0x80)
self.glyph_framebuffer = (bytearray(128), bytearray('~'*128), 0x40)
self.graphics_framebuffers = [(bytearray(32), bytearray('~'*32), i)
for i in range(32)]
self.framebuffers = ([self.text_framebuffer, self.glyph_framebuffer]
+ self.graphics_framebuffers)
def build_config(self):
self.mcu.add_config_cmd(
"config_st7920 oid=%u cs_pin=%s sclk_pin=%s sid_pin=%s"
" delay_ticks=%d" % (
self.oid, self.pins[0], self.pins[1], self.pins[2],
self.mcu.seconds_to_clock(ST7920_DELAY)))
cmd_queue = self.mcu.alloc_command_queue()
self.send_cmds_cmd = self.mcu.lookup_command(
"st7920_send_cmds oid=%c cmds=%*s", cq=cmd_queue)
self.send_data_cmd = self.mcu.lookup_command(
"st7920_send_data oid=%c data=%*s", cq=cmd_queue)
def send(self, cmds, is_data=False, is_extended=False):
cmd_type = self.send_cmds_cmd
if is_data:
cmd_type = self.send_data_cmd
elif self.is_extended != is_extended:
add_cmd = 0x22
if is_extended:
add_cmd = 0x26
cmds = [add_cmd] + cmds
self.is_extended = is_extended
cmd_type.send([self.oid, cmds], reqclock=BACKGROUND_PRIORITY_CLOCK)
#logging.debug("st7920 %d %s", is_data, repr(cmds))
def flush(self):
# Find all differences in the framebuffers and send them to the chip
for new_data, old_data, fb_id in self.framebuffers:
if new_data == old_data:
continue
# Find the position of all changed bytes in this framebuffer
diffs = [[i, 1] for i, (nd, od) in enumerate(zip(new_data, old_data))
if nd != od]
# Batch together changes that are close to each other
for i in range(len(diffs)-2, -1, -1):
pos, count = diffs[i]
nextpos, nextcount = diffs[i+1]
if pos + 5 >= nextpos and nextcount < 16:
diffs[i][1] = nextcount + (nextpos - pos)
del diffs[i+1]
# Transmit changes
for pos, count in diffs:
count += pos & 0x01
count += count & 0x01
pos = pos & ~0x01
chip_pos = pos >> 1
if fb_id < 0x40:
# Graphics framebuffer update
self.send([0x80 + fb_id, 0x80 + chip_pos], is_extended=True)
else:
self.send([fb_id + chip_pos])
self.send(new_data[pos:pos+count], is_data=True)
old_data[:] = new_data
def init(self):
cmds = [0x24, # Enter extended mode
0x40, # Clear vertical scroll address
0x02, # Enable CGRAM access
0x26, # Enable graphics
0x22, # Leave extended mode
0x02, # Home the display
0x06, # Set positive update direction
0x0c] # Enable display and hide cursor
self.send(cmds)
self.flush()
def load_glyph(self, glyph_id, data):
if len(data) > 32:
data = data[:32]
pos = min(glyph_id * 32, 96)
self.glyph_framebuffer[0][pos:pos+len(data)] = data
def write_text(self, x, y, data):
if x + len(data) > 16:
data = data[:16 - min(x, 16)]
pos = [0, 32, 16, 48][y] + x
self.text_framebuffer[0][pos:pos+len(data)] = data
def write_graphics(self, x, y, row, data):
if x + len(data) > 16:
data = data[:16 - min(x, 16)]
gfx_fb = y * 16 + row
if gfx_fb >= 32:
gfx_fb -= 32
x += 16
self.graphics_framebuffers[gfx_fb][0][x:x+len(data)] = data
def clear(self):
self.text_framebuffer[0][:] = ' '*64
zeros = bytearray(32)
for new_data, old_data, fb_id in self.graphics_framebuffers:
new_data[:] = zeros
######################################################################
# Icons
######################################################################
nozzle_icon = [
0b0000000000000000,
0b0000000000000000,
0b0000111111110000,
0b0001111111111000,
0b0001111111111000,
0b0001111111111000,
0b0000111111110000,
0b0000111111110000,
0b0001111111111000,
0b0001111111111000,
0b0001111111111000,
0b0000011111100000,
0b0000001111000000,
0b0000000110000000,
0b0000000000000000,
0b0000000000000000
]
bed_icon = [
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0111111111111110,
0b0111111111111110,
0b0000000000000000,
0b0000000000000000
]
heat1_icon = [
0b0000000000000000,
0b0000000000000000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0010001000100000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000
]
heat2_icon = [
0b0000000000000000,
0b0000000000000000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0010001000100000,
0b0010001000100000,
0b0001000100010000,
0b0000100010001000,
0b0000100010001000,
0b0001000100010000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000,
0b0000000000000000
]
fan1_icon = [
0b0000000000000000,
0b0111111111111110,
0b0111000000001110,
0b0110001111000110,
0b0100001111000010,
0b0100000110000010,
0b0101100000011010,
0b0101110110111010,
0b0101100000011010,
0b0100000110000010,
0b0100001111000010,
0b0110001111000110,
0b0111000000001110,
0b0111111111111110,
0b0000000000000000,
0b0000000000000000
]
fan2_icon = [
0b0000000000000000,
0b0111111111111110,
0b0111000000001110,
0b0110010000100110,
0b0100111001110010,
0b0101111001111010,
0b0100110000110010,
0b0100000110000010,
0b0100110000110010,
0b0101111001111010,
0b0100111001110010,
0b0110010000100110,
0b0111000000001110,
0b0111111111111110,
0b0000000000000000,
0b0000000000000000
]
feedrate_icon = [
0b0000000000000000,
0b0111111000000000,
0b0100000000000000,
0b0100000000000000,
0b0100000000000000,
0b0111111011111000,
0b0100000010000100,
0b0100000010000100,
0b0100000010000100,
0b0100000011111000,
0b0000000010001000,
0b0000000010000100,
0b0000000010000100,
0b0000000010000010,
0b0000000000000000,
0b0000000000000000
]
######################################################################
# LCD screen updates
######################################################################
LCD_chips = { 'st7920': ST7920, 'hd44780': HD44780 }
class PrinterLCD:
def __init__(self, config):
self.printer = config.get_printer()
self.reactor = self.printer.get_reactor()
self.lcd_chip = config.getchoice('lcd_type', LCD_chips)(config)
self.lcd_type = config.get('lcd_type')
# printer objects
self.gcode = self.toolhead = self.sdcard = None
self.fan = self.extruder0 = self.extruder1 = self.heater_bed = None
# screen updating
self.screen_update_timer = self.reactor.register_timer(
self.screen_update_event)
# Initialization
FAN1_GLYPH, FAN2_GLYPH, BED1_GLYPH, BED2_GLYPH = 0, 1, 2, 3
def printer_state(self, state):
if state == 'ready':
self.lcd_chip.init()
# Load printer objects
self.gcode = self.printer.lookup_object('gcode')
self.toolhead = self.printer.lookup_object('toolhead')
self.sdcard = self.printer.lookup_object('virtual_sdcard', None)
self.fan = self.printer.lookup_object('fan', None)
self.extruder0 = self.printer.lookup_object('extruder0', None)
self.extruder1 = self.printer.lookup_object('extruder1', None)
self.heater_bed = self.printer.lookup_object('heater_bed', None)
# Load glyphs
self.load_glyph(self.BED1_GLYPH, heat1_icon)
self.load_glyph(self.BED2_GLYPH, heat2_icon)
self.load_glyph(self.FAN1_GLYPH, fan1_icon)
self.load_glyph(self.FAN2_GLYPH, fan2_icon)
# Start screen update timer
self.reactor.update_timer(self.screen_update_timer, self.reactor.NOW)
# ST7920 Glyphs
def load_glyph(self, glyph_id, data):
if self.lcd_type != 'st7920':
return
glyph = [0x00] * (len(data) * 2)
for i, bits in enumerate(data):
glyph[i*2] = (bits >> 8) & 0xff
glyph[i*2 + 1] = bits & 0xff
return self.lcd_chip.load_glyph(glyph_id, glyph)
def animate_glyphs(self, eventtime, x, y, glyph_id, do_animate):
frame = do_animate and int(eventtime) & 1
self.lcd_chip.write_text(x, y, (0, (glyph_id + frame)*2))
# Graphics drawing
def draw_icon(self, x, y, data):
for i, bits in enumerate(data):
self.lcd_chip.write_graphics(
x, y, i, [(bits >> 8) & 0xff, bits & 0xff])
def draw_progress_bar(self, x, y, width, value):
value = int(value * 100.)
data = [0x00] * width
char_pcnt = int(100/width)
for i in range(width):
if (i+1)*char_pcnt <= value:
# Draw completely filled bytes
data[i] |= 0xFF
elif (i*char_pcnt) < value:
# Draw partially filled bytes
data[i] |= (-1 << 8-((value % char_pcnt)*8/char_pcnt)) & 0xff
data[0] |= 0x80
data[-1] |= 0x01
self.lcd_chip.write_graphics(x, y, 0, [0xff]*width)
for i in range(1, 15):
self.lcd_chip.write_graphics(x, y, i, data)
self.lcd_chip.write_graphics(x, y, 15, [0xff]*width)
# Screen updating
def screen_update_event(self, eventtime):
self.lcd_chip.clear()
if self.lcd_type == 'hd44780':
self.screen_update_hd44780(eventtime)
else:
self.screen_update_st7920(eventtime)
self.lcd_chip.flush()
return eventtime + .500
def screen_update_hd44780(self, eventtime):
lcd_chip = self.lcd_chip
# Heaters
if self.extruder0 is not None:
info = self.extruder0.get_heater().get_status(eventtime)
lcd_chip.write_text(0, 0, lcd_chip.char_thermometer)
self.draw_heater(1, 0, info)
if self.extruder1 is not None:
info = self.extruder1.get_heater().get_status(eventtime)
lcd_chip.write_text(0, 1, lcd_chip.char_thermometer)
self.draw_heater(1, 1, info)
if self.heater_bed is not None:
info = self.heater_bed.get_status(eventtime)
lcd_chip.write_text(10, 0, lcd_chip.char_heater_bed)
self.draw_heater(11, 0, info)
# Fan speed
if self.fan is not None:
info = self.fan.get_status(eventtime)
lcd_chip.write_text(10, 1, "Fan")
self.draw_percent(14, 1, 4, info['speed'])
# G-Code speed factor
gcode_info = self.gcode.get_status(eventtime)
lcd_chip.write_text(0, 2, lcd_chip.char_speed_factor)
self.draw_percent(1, 2, 4, gcode_info['speed_factor'])
# SD card print progress
if self.sdcard is not None:
info = self.sdcard.get_status(eventtime)
lcd_chip.write_text(7, 2, "SD")
self.draw_percent(9, 2, 4, info['progress'])
# Printing time and status
toolhead_info = self.toolhead.get_status(eventtime)
lcd_chip.write_text(14, 2, lcd_chip.char_clock)
self.draw_time(15, 2, toolhead_info['printing_time'])
self.draw_status(0, 3, gcode_info, toolhead_info)
def screen_update_st7920(self, eventtime):
# Heaters
if self.extruder0 is not None:
info = self.extruder0.get_heater().get_status(eventtime)
self.draw_icon(0, 0, nozzle_icon)
self.draw_heater(2, 0, info)
extruder_count = 1
if self.extruder1 is not None:
info = self.extruder1.get_heater().get_status(eventtime)
self.draw_icon(0, 1, nozzle_icon)
self.draw_heater(2, 1, info)
extruder_count = 2
if self.heater_bed is not None:
info = self.heater_bed.get_status(eventtime)
self.draw_icon(0, extruder_count, bed_icon)
if info['target']:
self.animate_glyphs(eventtime, 0, extruder_count,
self.BED1_GLYPH, True)
self.draw_heater(2, extruder_count, info)
# Fan speed
if self.fan is not None:
info = self.fan.get_status(eventtime)
self.animate_glyphs(eventtime, 10, 0, self.FAN1_GLYPH,
info['speed'] != 0.)
self.draw_percent(12, 0, 4, info['speed'])
# SD card print progress
if self.sdcard is not None:
info = self.sdcard.get_status(eventtime)
if extruder_count == 1:
x, y, width = 0, 2, 10
else:
x, y, width = 10, 1, 6
self.draw_percent(x, y, width, info['progress'])
self.draw_progress_bar(x, y, width, info['progress'])
# G-Code speed factor
gcode_info = self.gcode.get_status(eventtime)
if extruder_count == 1:
self.draw_icon(10, 1, feedrate_icon)
self.draw_percent(12, 1, 4, gcode_info['speed_factor'])
# Printing time and status
toolhead_info = self.toolhead.get_status(eventtime)
self.draw_time(10, 2, toolhead_info['printing_time'])
self.draw_status(0, 3, gcode_info, toolhead_info)
# Screen update helpers
def draw_heater(self, x, y, info):
temperature, target = info['temperature'], info['target']
if target and abs(temperature - target) > 2.:
s = "%3.0f%s%.0f" % (
temperature, self.lcd_chip.char_right_arrow, target)
else:
s = "%3.0f" % (temperature,)
if self.lcd_type == 'hd44780':
s += self.lcd_chip.char_degrees
self.lcd_chip.write_text(x, y, s)
def draw_percent(self, x, y, width, value):
self.lcd_chip.write_text(x, y, ("%d%%" % (value * 100.,)).center(width))
def draw_time(self, x, y, seconds):
seconds = int(seconds)
self.lcd_chip.write_text(x, y, "%02d:%02d" % (
seconds // (60 * 60), (seconds // 60) % 60))
def draw_status(self, x, y, gcode_info, toolhead_info):
status = toolhead_info['status']
if status == 'Printing' or gcode_info['busy']:
pos = self.toolhead.get_position()
status = "X%-4.0fY%-4.0fZ%-5.2f" % (pos[0], pos[1], pos[2])
self.lcd_chip.write_text(x, y, status)
def load_config(config):
return PrinterLCD(config)

39
klippy/extras/fan.py Normal file
View File

@@ -0,0 +1,39 @@
# Printer cooling fan
#
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins
FAN_MIN_TIME = 0.100
class PrinterFan:
def __init__(self, config):
self.last_fan_value = 0.
self.last_fan_time = 0.
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
printer = config.get_printer()
self.mcu_fan = pins.setup_pin(printer, 'pwm', config.get('pin'))
self.mcu_fan.setup_max_duration(0.)
cycle_time = config.getfloat('cycle_time', 0.010, above=0.)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.mcu_fan.setup_cycle_time(cycle_time, hardware_pwm)
def set_speed(self, print_time, value):
value = max(0., min(self.max_power, value))
if value == self.last_fan_value:
return
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
if (value and value < self.max_power
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(print_time, self.max_power)
print_time += self.kick_start_time
self.mcu_fan.set_pwm(print_time, value)
self.last_fan_time = print_time
self.last_fan_value = value
def get_status(self, eventtime):
return {'speed': self.last_fan_value}
def load_config(config):
return PrinterFan(config)

View File

@@ -0,0 +1,37 @@
# Support fans that are enabled when a heater is on
#
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import fan, extruder
PIN_MIN_TIME = 0.100
class PrinterHeaterFan:
def __init__(self, config):
self.printer = config.get_printer()
self.heater_name = config.get("heater", "extruder0")
self.heater_temp = config.getfloat("heater_temp", 50.0)
self.fan = fan.PrinterFan(config)
self.mcu = self.fan.mcu_fan.get_mcu()
max_power = self.fan.max_power
self.fan_speed = config.getfloat(
"fan_speed", max_power, minval=0., maxval=max_power)
self.fan.mcu_fan.setup_start_value(0., max_power)
def printer_state(self, state):
if state == 'ready':
self.heater = extruder.get_printer_heater(
self.printer, self.heater_name)
reactor = self.printer.get_reactor()
reactor.register_timer(self.callback, reactor.NOW)
def callback(self, eventtime):
current_temp, target_temp = self.heater.get_temp(eventtime)
power = 0.
if target_temp or current_temp > self.heater_temp:
power = self.fan_speed
print_time = self.mcu.estimated_print_time(eventtime) + PIN_MIN_TIME
self.fan.set_speed(print_time, power)
return eventtime + 1.
def load_config_prefix(config):
return PrinterHeaterFan(config)

View File

@@ -0,0 +1,39 @@
# Run user defined actions in place of a normal G28 homing command
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
class HomingOverride:
def __init__(self, config):
self.printer = config.get_printer()
self.start_pos = [config.getfloat('set_position_' + a, None)
for a in 'xyz']
self.script = config.get('gcode')
self.in_script = False
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("G28", self.cmd_G28)
def cmd_G28(self, params):
if self.in_script:
# Was called recursively - invoke the real G28 command
self.gcode.cmd_G28(params)
return
# Calculate forced position (if configured)
toolhead = self.printer.lookup_object('toolhead')
pos = toolhead.get_position()
homing_axes = []
for axis, loc in enumerate(self.start_pos):
if loc is not None:
pos[axis] = loc
homing_axes.append(axis)
toolhead.set_position(pos, homing_axes=homing_axes)
self.gcode.reset_last_position()
# Perform homing
try:
self.in_script = True
self.gcode.run_script(self.script)
finally:
self.in_script = False
def load_config(config):
return HomingOverride(config)

View File

@@ -0,0 +1,54 @@
# Virtual pin that propagates its changes to multiple output pins
#
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins
class PrinterMultiPin:
def __init__(self, config):
self.printer = config.get_printer()
try:
pins.get_printer_pins(self.printer).register_chip('multi_pin', self)
except pins.error:
pass
self.pin_type = None
self.pin_list = [pin.strip() for pin in config.get('pins').split(',')]
self.mcu_pins = []
def setup_pin(self, pin_params):
pin_name = pin_params['pin']
pin = self.printer.lookup_object('multi_pin ' + pin_name, None)
if pin is not self:
if pin is None:
raise pins.error("multi_pin %s not configured" % (pin_name,))
return pin.setup_pin(pin_params)
if self.pin_type is not None:
raise pins.error("Can't setup multi_pin %s twice" % (pin_name,))
self.pin_type = pin_params['type']
invert = ""
if pin_params['invert']:
invert = "!"
self.mcu_pins = [
pins.setup_pin(self.printer, self.pin_type, invert + pin_desc)
for pin_desc in self.pin_list]
return self
def get_mcu(self):
return self.mcu_pins[0].get_mcu()
def setup_max_duration(self, max_duration):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_max_duration(max_duration)
def setup_start_value(self, start_value, shutdown_value):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_start_value(start_value, shutdown_value)
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
for mcu_pin in self.mcu_pins:
mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
def set_digital(self, print_time, value):
for mcu_pin in self.mcu_pins:
mcu_pin.set_digital(print_time, value)
def set_pwm(self, print_time, value):
for mcu_pin in self.mcu_pins:
mcu_pin.set_pwm(print_time, value)
def load_config_prefix(config):
return PrinterMultiPin(config)

View File

@@ -0,0 +1,69 @@
# Code to configure miscellaneous chips
#
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
PIN_MIN_TIME = 0.100
class PrinterOutputPin:
def __init__(self, config):
self.printer = config.get_printer()
ppins = self.printer.lookup_object('pins')
self.is_pwm = config.getboolean('pwm', False)
if self.is_pwm:
self.mcu_pin = ppins.setup_pin('pwm', config.get('pin'))
cycle_time = config.getfloat('cycle_time', 0.100, above=0.)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
self.scale = config.getfloat('scale', 1., above=0.)
else:
self.mcu_pin = ppins.setup_pin('digital_out', config.get('pin'))
self.scale = 1.
self.mcu_pin.setup_max_duration(0.)
self.last_value_time = 0.
static_value = config.getfloat('static_value', None,
minval=0., maxval=self.scale)
if static_value is not None:
self.is_static = True
self.last_value = static_value / self.scale
self.mcu_pin.setup_start_value(
self.last_value, self.last_value, True)
else:
self.is_static = False
self.last_value = config.getfloat(
'value', 0., minval=0., maxval=self.scale) / self.scale
shutdown_value = config.getfloat(
'shutdown_value', 0., minval=0., maxval=self.scale) / self.scale
self.mcu_pin.setup_start_value(self.last_value, shutdown_value)
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("SET_PIN", self.cmd_SET_PIN,
desc=self.cmd_SET_PIN_help)
cmd_SET_PIN_help = "Set the value of an output pin"
def cmd_SET_PIN(self, params):
pin_name = self.gcode.get_str('PIN', params)
pin = self.printer.lookup_object('output_pin ' + pin_name, None)
if pin is not self:
if pin is None:
raise self.gcode.error("Pin not configured")
return pin.cmd_SET_PIN(params)
if self.is_static:
raise self.gcode.error("Static pin can not be changed at run-time")
value = self.gcode.get_float('VALUE', params) / self.scale
if value == self.last_value:
return
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
print_time = max(print_time, self.last_value_time + PIN_MIN_TIME)
if self.is_pwm:
if value < 0. or value > 1.:
raise self.gcode.error("Invalid pin value")
self.mcu_pin.set_pwm(print_time, value)
else:
if value not in [0., 1.]:
raise self.gcode.error("Invalid pin value")
self.mcu_pin.set_digital(print_time, value)
self.last_value = value
self.last_value_time = print_time
def load_config_prefix(config):
return PrinterOutputPin(config)

View File

@@ -0,0 +1,127 @@
# Calibration of heater PID settings
#
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
import extruder, heater
class PIDCalibrate:
def __init__(self, config):
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'PID_CALIBRATE', self.cmd_PID_CALIBRATE,
desc=self.cmd_PID_CALIBRATE_help)
cmd_PID_CALIBRATE_help = "Run PID calibration test"
def cmd_PID_CALIBRATE(self, params):
heater_name = self.gcode.get_str('HEATER', params)
target = self.gcode.get_float('TARGET', params)
write_file = self.gcode.get_int('WRITE_FILE', params, 0)
try:
heater = extruder.get_printer_heater(self.printer, heater_name)
except self.printer.config_error as e:
raise self.gcode.error(str(e))
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
calibrate = ControlAutoTune(heater)
old_control = heater.set_control(calibrate)
try:
heater.set_temp(print_time, target)
except heater.error as e:
raise self.gcode.error(str(e))
self.gcode.bg_temp(heater)
heater.set_control(old_control)
if write_file:
calibrate.write_file('/tmp/heattest.txt')
Kp, Ki, Kd = calibrate.calc_final_pid()
logging.info("Autotune: final: Kp=%f Ki=%f Kd=%f", Kp, Ki, Kd)
self.gcode.respond_info(
"PID parameters: pid_Kp=%.3f pid_Ki=%.3f pid_Kd=%.3f\n"
"To use these parameters, update the printer config file with\n"
"the above and then issue a RESTART command" % (Kp, Ki, Kd))
TUNE_PID_DELTA = 5.0
class ControlAutoTune:
def __init__(self, heater):
self.heater = heater
# Heating control
self.heating = False
self.peak = 0.
self.peak_time = 0.
# Peak recording
self.peaks = []
# Sample recording
self.last_pwm = 0.
self.pwm_samples = []
self.temp_samples = []
# Heater control
def set_pwm(self, read_time, value):
if value != self.last_pwm:
self.pwm_samples.append((read_time + heater.PWM_DELAY, value))
self.last_pwm = value
self.heater.set_pwm(read_time, value)
def adc_callback(self, read_time, temp):
self.temp_samples.append((read_time, temp))
if self.heating and temp >= self.heater.target_temp:
self.heating = False
self.check_peaks()
elif (not self.heating
and temp <= self.heater.target_temp - TUNE_PID_DELTA):
self.heating = True
self.check_peaks()
if self.heating:
self.set_pwm(read_time, self.heater.max_power)
if temp < self.peak:
self.peak = temp
self.peak_time = read_time
else:
self.set_pwm(read_time, 0.)
if temp > self.peak:
self.peak = temp
self.peak_time = read_time
def check_busy(self, eventtime):
if self.heating or len(self.peaks) < 12:
return True
return False
# Analysis
def check_peaks(self):
self.peaks.append((self.peak, self.peak_time))
if self.heating:
self.peak = 9999999.
else:
self.peak = -9999999.
if len(self.peaks) < 4:
return
self.calc_pid(len(self.peaks)-1)
def calc_pid(self, pos):
temp_diff = self.peaks[pos][0] - self.peaks[pos-1][0]
time_diff = self.peaks[pos][1] - self.peaks[pos-2][1]
max_power = self.heater.max_power
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
Tu = time_diff
Ti = 0.5 * Tu
Td = 0.125 * Tu
Kp = 0.6 * Ku * heater.PID_PARAM_BASE
Ki = Kp / Ti
Kd = Kp * Td
logging.info("Autotune: raw=%f/%f Ku=%f Tu=%f Kp=%f Ki=%f Kd=%f",
temp_diff, max_power, Ku, Tu, Kp, Ki, Kd)
return Kp, Ki, Kd
def calc_final_pid(self):
cycle_times = [(self.peaks[pos][1] - self.peaks[pos-2][1], pos)
for pos in range(4, len(self.peaks))]
midpoint_pos = sorted(cycle_times)[len(cycle_times)/2][1]
return self.calc_pid(midpoint_pos)
# Offline analysis helper
def write_file(self, filename):
pwm = ["pwm: %.3f %.3f" % (time, value)
for time, value in self.pwm_samples]
out = ["%.3f %.3f" % (time, temp) for time, temp in self.temp_samples]
f = open(filename, "wb")
f.write('\n'.join(pwm + out))
f.close()
def load_config(config):
return PIDCalibrate(config)

189
klippy/extras/probe.py Normal file
View File

@@ -0,0 +1,189 @@
# Z-Probe support
#
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins, homing
HINT_TIMEOUT = """
Make sure to home the printer before probing. If the probe
did not move far enough to trigger, then consider reducing
the Z axis minimum position so the probe can travel further
(the Z minimum position can be negative).
"""
class PrinterProbe:
def __init__(self, config):
self.printer = config.get_printer()
self.speed = config.getfloat('speed', 5.0)
self.z_offset = config.getfloat('z_offset')
# Infer Z position to move to during a probe
if config.has_section('stepper_z'):
zconfig = config.getsection('stepper_z')
self.z_position = zconfig.getfloat('position_min', 0.)
else:
pconfig = config.getsection('printer')
self.z_position = pconfig.getfloat('minimum_z_position', 0.)
# Create an "endstop" object to handle the probe pin
ppins = self.printer.lookup_object('pins')
pin_params = ppins.lookup_pin('endstop', config.get('pin'))
mcu = pin_params['chip']
mcu.add_config_object(self)
self.mcu_probe = mcu.setup_pin(pin_params)
if (config.get('activate_gcode', None) is not None or
config.get('deactivate_gcode', None) is not None):
self.mcu_probe = ProbeEndstopWrapper(config, self.mcu_probe)
# Create z_virtual_endstop pin
ppins.register_chip('probe', self)
self.z_virtual_endstop = None
# Register PROBE/QUERY_PROBE commands
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'PROBE', self.cmd_PROBE, desc=self.cmd_PROBE_help)
self.gcode.register_command(
'QUERY_PROBE', self.cmd_QUERY_PROBE, desc=self.cmd_QUERY_PROBE_help)
def build_config(self):
toolhead = self.printer.lookup_object('toolhead')
z_steppers = toolhead.get_kinematics().get_steppers("Z")
for s in z_steppers:
for mcu_endstop, name in s.get_endstops():
for mcu_stepper in mcu_endstop.get_steppers():
self.mcu_probe.add_stepper(mcu_stepper)
def setup_pin(self, pin_params):
if (pin_params['pin'] != 'z_virtual_endstop'
or pin_params['type'] != 'endstop'):
raise pins.error("Probe virtual endstop only useful as endstop pin")
if pin_params['invert'] or pin_params['pullup']:
raise pins.error("Can not pullup/invert probe virtual endstop")
self.z_virtual_endstop = ProbeVirtualEndstop(
self.printer, self.mcu_probe)
return self.z_virtual_endstop
def last_home_position(self):
if self.z_virtual_endstop is None:
return None
return self.z_virtual_endstop.position
cmd_PROBE_help = "Probe Z-height at current XY position"
def cmd_PROBE(self, params):
toolhead = self.printer.lookup_object('toolhead')
homing_state = homing.Homing(toolhead)
pos = toolhead.get_position()
pos[2] = self.z_position
try:
homing_state.homing_move(
pos, [(self.mcu_probe, "probe")], self.speed, probe_pos=True)
except homing.EndstopError as e:
reason = str(e)
if "Timeout during endstop homing" in reason:
reason += HINT_TIMEOUT
raise self.gcode.error(reason)
self.gcode.reset_last_position()
cmd_QUERY_PROBE_help = "Return the status of the z-probe"
def cmd_QUERY_PROBE(self, params):
toolhead = self.printer.lookup_object('toolhead')
print_time = toolhead.get_last_move_time()
self.mcu_probe.query_endstop(print_time)
res = self.mcu_probe.query_endstop_wait()
self.gcode.respond_info(
"probe: %s" % (["open", "TRIGGERED"][not not res],))
# Endstop wrapper that enables running g-code scripts on setup
class ProbeEndstopWrapper:
def __init__(self, config, mcu_endstop):
self.mcu_endstop = mcu_endstop
self.gcode = config.get_printer().lookup_object('gcode')
self.activate_gcode = config.get('activate_gcode', "")
self.deactivate_gcode = config.get('deactivate_gcode', "")
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
self.get_steppers = self.mcu_endstop.get_steppers
self.home_start = self.mcu_endstop.home_start
self.home_wait = self.mcu_endstop.home_wait
self.query_endstop = self.mcu_endstop.query_endstop
self.query_endstop_wait = self.mcu_endstop.query_endstop_wait
self.TimeoutError = self.mcu_endstop.TimeoutError
def home_prepare(self):
self.gcode.run_script(self.activate_gcode)
self.mcu_endstop.home_prepare()
def home_finalize(self):
self.gcode.run_script(self.deactivate_gcode)
self.mcu_endstop.home_finalize()
# Wrapper that records the last XY position of a virtual endstop probe
class ProbeVirtualEndstop:
def __init__(self, printer, mcu_endstop):
self.printer = printer
self.mcu_endstop = mcu_endstop
self.position = None
# Wrappers
self.get_mcu = self.mcu_endstop.get_mcu
self.add_stepper = self.mcu_endstop.add_stepper
self.get_steppers = self.mcu_endstop.get_steppers
self.home_start = self.mcu_endstop.home_start
self.home_wait = self.mcu_endstop.home_wait
self.query_endstop = self.mcu_endstop.query_endstop
self.query_endstop_wait = self.mcu_endstop.query_endstop_wait
self.home_prepare = self.mcu_endstop.home_prepare
self.TimeoutError = self.mcu_endstop.TimeoutError
def home_finalize(self):
self.position = self.printer.lookup_object('toolhead').get_position()
self.mcu_endstop.home_finalize()
# Helper code that can probe a series of points and report the
# position at each point.
class ProbePointsHelper:
def __init__(self, printer, probe_points, horizontal_move_z, speed,
manual_probe, callback):
self.printer = printer
self.probe_points = probe_points
self.horizontal_move_z = horizontal_move_z
self.speed = speed
self.manual_probe = manual_probe
self.callback = callback
self.toolhead = self.printer.lookup_object('toolhead')
self.results = []
self.busy = True
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command(
'NEXT', self.cmd_NEXT, desc=self.cmd_NEXT_help)
# Begin probing
self.move_next()
if not manual_probe:
while self.busy:
self.gcode.run_script("PROBE")
self.cmd_NEXT({})
def move_next(self):
x, y = self.probe_points[len(self.results)]
curpos = self.toolhead.get_position()
curpos[0] = x
curpos[1] = y
curpos[2] = self.horizontal_move_z
self.toolhead.move(curpos, self.speed)
self.gcode.reset_last_position()
cmd_NEXT_help = "Move to the next XY position to probe"
def cmd_NEXT(self, params):
# Record current position
self.toolhead.wait_moves()
self.results.append(self.callback.get_position())
# Move to next position
curpos = self.toolhead.get_position()
curpos[2] = self.horizontal_move_z
self.toolhead.move(curpos, self.speed)
if len(self.results) == len(self.probe_points):
self.toolhead.get_last_move_time()
self.finalize(True)
return
self.move_next()
def finalize(self, success):
self.busy = False
self.gcode.reset_last_position()
self.gcode.register_command('NEXT', None)
if success:
z_offset = 0.
if not self.manual_probe:
probe = self.printer.lookup_object('probe')
z_offset = probe.z_offset
self.callback.finalize(z_offset, self.results)
def load_config(config):
return PrinterProbe(config)

229
klippy/extras/replicape.py Normal file
View File

@@ -0,0 +1,229 @@
# Code to configure miscellaneous chips
#
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import pins, mcu
REPLICAPE_MAX_CURRENT = 3.84
REPLICAPE_SHIFT_REGISTER_BUS = 1
REPLICAPE_SHIFT_REGISTER_DEVICE = 1
REPLICAPE_PCA9685_BUS = 2
REPLICAPE_PCA9685_ADDRESS = 0x70
REPLICAPE_PCA9685_CYCLE_TIME = .001
PIN_MIN_TIME = 0.100
class pca9685_pwm:
def __init__(self, replicape, channel, pin_params):
self._replicape = replicape
self._channel = channel
if pin_params['type'] not in ['digital_out', 'pwm']:
raise pins.error("Pin type not supported on replicape")
self._mcu = replicape.host_mcu
self._mcu.add_config_object(self)
self._bus = REPLICAPE_PCA9685_BUS
self._address = REPLICAPE_PCA9685_ADDRESS
self._cycle_time = REPLICAPE_PCA9685_CYCLE_TIME
self._max_duration = 2.
self._oid = None
self._invert = pin_params['invert']
self._start_value = self._shutdown_value = float(self._invert)
self._is_static = False
self._last_clock = 0
self._pwm_max = 0.
self._set_cmd = None
def get_mcu(self):
return self._mcu
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
if hardware_pwm:
raise pins.error("pca9685 does not support hardware_pwm parameter")
if cycle_time != self._cycle_time:
logging.info("Ignoring pca9685 cycle time of %.6f (using %.6f)",
cycle_time, self._cycle_time)
def setup_start_value(self, start_value, shutdown_value, is_static=False):
if is_static and start_value != shutdown_value:
raise pins.error("Static pin can not have shutdown value")
if self._invert:
start_value = 1. - start_value
shutdown_value = 1. - shutdown_value
self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_value))
self._is_static = is_static
self._replicape.note_pwm_start_value(
self._channel, self._start_value, self._shutdown_value)
def build_config(self):
self._pwm_max = self._mcu.get_constant_float("PCA9685_MAX")
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
if self._is_static:
self._mcu.add_config_cmd(
"set_pca9685_out bus=%d addr=%d channel=%d"
" cycle_ticks=%d value=%d" % (
self._bus, self._address, self._channel,
cycle_ticks, self._start_value * self._pwm_max))
return
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_pca9685 oid=%d bus=%d addr=%d channel=%d cycle_ticks=%d"
" value=%d default_value=%d max_duration=%d" % (
self._oid, self._bus, self._address, self._channel, cycle_ticks,
self._start_value * self._pwm_max,
self._shutdown_value * self._pwm_max,
self._mcu.seconds_to_clock(self._max_duration)))
cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = self._mcu.lookup_command(
"schedule_pca9685_out oid=%c clock=%u value=%hu", cq=cmd_queue)
def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
if self._invert:
value = 1. - value
value = int(max(0., min(1., value)) * self._pwm_max + 0.5)
self._replicape.note_pwm_enable(print_time, self._channel, value)
self._set_cmd.send([self._oid, clock, value],
minclock=self._last_clock, reqclock=clock)
self._last_clock = clock
def set_digital(self, print_time, value):
if value:
self.set_pwm(print_time, 1.)
else:
self.set_pwm(print_time, 0.)
class ReplicapeDACEnable:
def __init__(self, replicape, channel, pin_params):
if pin_params['type'] != 'digital_out':
raise pins.error("Replicape virtual enable pin must be digital_out")
if pin_params['invert']:
raise pins.error("Replicape virtual enable pin can not be inverted")
self.mcu = replicape.host_mcu
self.value = replicape.stepper_dacs[channel]
self.pwm = pca9685_pwm(replicape, channel, pin_params)
def get_mcu(self):
return self.mcu
def setup_max_duration(self, max_duration):
self.pwm.setup_max_duration(max_duration)
def set_digital(self, print_time, value):
if value:
self.pwm.set_pwm(print_time, self.value)
else:
self.pwm.set_pwm(print_time, 0.)
ReplicapeStepConfig = {
'disable': None,
'1': (1<<7)|(1<<5), '2': (1<<7)|(1<<5)|(1<<6), 'spread2': (1<<5),
'4': (1<<7)|(1<<5)|(1<<4), '16': (1<<7)|(1<<5)|(1<<6)|(1<<4),
'spread4': (1<<5)|(1<<4), 'spread16': (1<<7), 'stealth4': (1<<7)|(1<<6),
'stealth16': 0
}
class Replicape:
def __init__(self, config):
printer = config.get_printer()
pins.get_printer_pins(printer).register_chip('replicape', self)
revisions = {'B3': 'B3'}
config.getchoice('revision', revisions)
self.host_mcu = mcu.get_printer_mcu(printer, config.get('host_mcu'))
# Setup enable pin
self.mcu_pwm_enable = pins.setup_pin(
printer, 'digital_out', config.get('enable_pin', '!P9_41'))
self.mcu_pwm_enable.setup_max_duration(0.)
self.mcu_pwm_start_value = self.mcu_pwm_shutdown_value = False
# Setup power pins
self.pins = {
"power_e": (pca9685_pwm, 5), "power_h": (pca9685_pwm, 3),
"power_hotbed": (pca9685_pwm, 4),
"power_fan0": (pca9685_pwm, 7), "power_fan1": (pca9685_pwm, 8),
"power_fan2": (pca9685_pwm, 9), "power_fan3": (pca9685_pwm, 10) }
# Setup stepper config
self.send_spi_cmd = None
self.last_stepper_time = 0.
self.stepper_dacs = {}
shift_registers = [1, 0, 0, 1, 1]
for port, name in enumerate('xyzeh'):
prefix = 'stepper_%s_' % (name,)
sc = config.getchoice(
prefix + 'microstep_mode', ReplicapeStepConfig, 'disable')
if sc is None:
continue
sc |= shift_registers[port]
if config.getboolean(prefix + 'chopper_off_time_high', False):
sc |= 1<<3
if config.getboolean(prefix + 'chopper_hysteresis_high', False):
sc |= 1<<2
if config.getboolean(prefix + 'chopper_blank_time_high', True):
sc |= 1<<1
shift_registers[port] = sc
channel = port + 11
cur = config.getfloat(
prefix + 'current', above=0., maxval=REPLICAPE_MAX_CURRENT)
self.stepper_dacs[channel] = cur / REPLICAPE_MAX_CURRENT
self.pins[prefix + 'enable'] = (ReplicapeDACEnable, channel)
self.enabled_channels = {ch: False for cl, ch in self.pins.values()}
if config.getboolean('servo0_enable', False):
shift_registers[1] |= 1
if config.getboolean('servo1_enable', False):
shift_registers[2] |= 1
self.sr_disabled = tuple(reversed(shift_registers))
if [i for i in [0, 1, 2] if 11+i in self.stepper_dacs]:
# Enable xyz steppers
shift_registers[0] &= ~1
if [i for i in [3, 4] if 11+i in self.stepper_dacs]:
# Enable eh steppers
shift_registers[3] &= ~1
if (config.getboolean('standstill_power_down', False)
and self.stepper_dacs):
shift_registers[4] &= ~1
self.sr_enabled = tuple(reversed(shift_registers))
self.host_mcu.add_config_object(self)
self.host_mcu.add_config_cmd("send_spi bus=%d dev=%d msg=%s" % (
REPLICAPE_SHIFT_REGISTER_BUS, REPLICAPE_SHIFT_REGISTER_DEVICE,
"".join(["%02x" % (x,) for x in self.sr_disabled])))
def build_config(self):
cmd_queue = self.host_mcu.alloc_command_queue()
self.send_spi_cmd = self.host_mcu.lookup_command(
"send_spi bus=%u dev=%u msg=%*s", cq=cmd_queue)
def note_pwm_start_value(self, channel, start_value, shutdown_value):
self.mcu_pwm_start_value |= not not start_value
self.mcu_pwm_shutdown_value |= not not shutdown_value
self.mcu_pwm_enable.setup_start_value(
self.mcu_pwm_start_value, self.mcu_pwm_shutdown_value)
self.enabled_channels[channel] = not not start_value
def note_pwm_enable(self, print_time, channel, value):
is_enable = not not value
if self.enabled_channels[channel] == is_enable:
# Nothing to do
return
self.enabled_channels[channel] = is_enable
# Check if need to set the pca9685 enable pin
on_channels = [1 for c, e in self.enabled_channels.items() if e]
if not on_channels:
self.mcu_pwm_enable.set_digital(print_time, 0)
elif is_enable and len(on_channels) == 1:
self.mcu_pwm_enable.set_digital(print_time, 1)
# Check if need to set the stepper enable lines
if channel not in self.stepper_dacs:
return
on_dacs = [1 for c in self.stepper_dacs.keys()
if self.enabled_channels[c]]
if not on_dacs:
sr = self.sr_disabled
elif is_enable and len(on_dacs) == 1:
sr = self.sr_enabled
else:
return
print_time = max(print_time, self.last_stepper_time + PIN_MIN_TIME)
clock = self.host_mcu.print_time_to_clock(print_time)
# XXX - the send_spi message should be scheduled
self.send_spi_cmd.send([REPLICAPE_SHIFT_REGISTER_BUS,
REPLICAPE_SHIFT_REGISTER_DEVICE, sr],
minclock=clock, reqclock=clock)
def setup_pin(self, pin_params):
pin = pin_params['pin']
if pin not in self.pins:
raise pins.error("Unknown replicape pin %s" % (pin,))
pclass, channel = self.pins[pin]
return pclass(self, channel, pin_params)
def load_config(config):
return Replicape(config)

59
klippy/extras/servo.py Normal file
View File

@@ -0,0 +1,59 @@
# Support for servos
#
# Copyright (C) 2017,2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import pins
SERVO_SIGNAL_PERIOD = 0.020
PIN_MIN_TIME = 0.100
class PrinterServo:
def __init__(self, config):
self.printer = config.get_printer()
self.mcu_servo = pins.setup_pin(self.printer, 'pwm', config.get('pin'))
self.mcu_servo.setup_max_duration(0.)
self.mcu_servo.setup_cycle_time(SERVO_SIGNAL_PERIOD)
self.min_width = config.getfloat(
'minimum_pulse_width', .001, above=0., below=SERVO_SIGNAL_PERIOD)
self.max_width = config.getfloat(
'maximum_pulse_width', .002
, above=self.min_width, below=SERVO_SIGNAL_PERIOD)
self.max_angle = config.getfloat('maximum_servo_angle', 180.)
self.angle_to_width = (self.max_width - self.min_width) / self.max_angle
self.width_to_value = 1. / SERVO_SIGNAL_PERIOD
self.last_value = self.last_value_time = 0.
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command("SET_SERVO", self.cmd_SET_SERVO,
desc=self.cmd_SET_SERVO_help)
def set_pwm(self, print_time, value):
if value == self.last_value:
return
print_time = max(print_time, self.last_value_time + PIN_MIN_TIME)
self.mcu_servo.set_pwm(print_time, value)
self.last_value = value
self.last_value_time = print_time
def set_angle(self, print_time, angle):
angle = max(0., min(self.max_angle, angle))
width = self.min_width + angle * self.angle_to_width
self.set_pwm(print_time, width * self.width_to_value)
def set_pulse_width(self, print_time, width):
width = max(self.min_width, min(self.max_width, width))
self.set_pwm(print_time, width * self.width_to_value)
cmd_SET_SERVO_help = "Set servo angle"
def cmd_SET_SERVO(self, params):
servo_name = self.gcode.get_str('SERVO', params)
servo = self.printer.lookup_object('servo ' + servo_name, None)
if servo is not self:
if servo is None:
raise self.gcode.error("Servo not configured")
return servo.cmd_SET_SERVO(params)
print_time = self.printer.lookup_object('toolhead').get_last_move_time()
if 'WIDTH' in params:
self.set_pulse_width(print_time,
self.gcode.get_float('WIDTH', params))
else:
self.set_angle(print_time, self.gcode.get_float('ANGLE', params))
def load_config_prefix(config):
return PrinterServo(config)

View File

@@ -0,0 +1,17 @@
# Set the state of a list of digital output pins
#
# Copyright (C) 2017-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
class PrinterStaticDigitalOut:
def __init__(self, config):
printer = config.get_printer()
ppins = printer.lookup_object('pins')
pin_list = [pin.strip() for pin in config.get('pins').split(',')]
for pin_desc in pin_list:
mcu_pin = ppins.setup_pin('digital_out', pin_desc)
mcu_pin.setup_start_value(1, 1, True)
def load_config_prefix(config):
return PrinterStaticDigitalOut(config)

View File

@@ -0,0 +1,74 @@
# Heater/sensor verification code
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
import extruder
HINT_THERMAL = """
See the 'verify_heater' section in config/example-extras.cfg
for the parameters that control this check.
"""
class HeaterCheck:
def __init__(self, config):
self.printer = config.get_printer()
self.heater_name = config.get_name().split()[1]
self.heater = None
self.hysteresis = config.getfloat('hysteresis', 5., minval=0.)
self.max_error = config.getfloat('max_error', 120., minval=0.)
self.heating_gain = config.getfloat('heating_gain', 2., above=0.)
default_gain_time = 20.
if self.heater_name == 'heater_bed':
default_gain_time = 60.
self.check_gain_time = config.getfloat(
'check_gain_time', default_gain_time, minval=1.)
self.met_target = False
self.last_target = self.goal_temp = self.error = 0.
self.fault_systime = self.printer.get_reactor().NEVER
def printer_state(self, state):
if state == 'connect':
self.heater = extruder.get_printer_heater(
self.printer, self.heater_name)
logging.info("Starting heater checks for %s", self.heater_name)
reactor = self.printer.get_reactor()
reactor.register_timer(self.check_event, reactor.NOW)
def check_event(self, eventtime):
temp, target = self.heater.get_temp(eventtime)
if temp >= target - self.hysteresis:
# Temperature near target - reset checks
if not self.met_target and target:
logging.info("Heater %s within range of %.3f",
self.heater_name, target)
self.met_target = True
self.error = 0.
elif self.met_target:
self.error += (target - self.hysteresis) - temp
if target != self.last_target:
# Target changed - reset checks
logging.info("Heater %s approaching new target of %.3f",
self.heater_name, target)
self.met_target = False
self.goal_temp = temp + self.heating_gain
self.fault_systime = eventtime + self.check_gain_time
elif self.error >= self.max_error:
# Failure due to inability to maintain target temperature
return self.heater_fault()
elif temp >= self.goal_temp:
# Temperature approaching target - reset checks
self.goal_temp = temp + self.heating_gain
self.fault_systime = eventtime + self.check_gain_time
elif eventtime >= self.fault_systime:
# Failure due to inability to approach target temperature
return self.heater_fault()
self.last_target = target
return eventtime + 1.
def heater_fault(self):
msg = "Heater %s not heating at expected rate" % (self.heater_name,)
logging.error(msg)
self.printer.invoke_shutdown(msg + HINT_THERMAL)
return self.printer.get_reactor().NEVER
def load_config_prefix(config):
return HeaterCheck(config)

View File

@@ -0,0 +1,162 @@
# Virtual sdcard support (print files directly from a host g-code file)
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, logging
class VirtualSD:
def __init__(self, config):
printer = config.get_printer()
# sdcard state
sd = config.get('path')
self.sdcard_dirname = os.path.normpath(os.path.expanduser(sd))
self.current_file = None
self.file_position = self.file_size = 0
# Work timer
self.reactor = printer.get_reactor()
self.must_pause_work = False
self.work_timer = None
# Register commands
self.gcode = printer.lookup_object('gcode')
for cmd in ['M20', 'M21', 'M23', 'M24', 'M25', 'M26', 'M27']:
self.gcode.register_command(cmd, getattr(self, 'cmd_' + cmd))
for cmd in ['M28', 'M29', 'M30']:
self.gcode.register_command(cmd, self.cmd_error)
def printer_state(self, state):
if state == 'shutdown' and self.work_timer is not None:
self.must_pause_work = True
def get_file_list(self):
dname = self.sdcard_dirname
try:
filenames = os.listdir(self.sdcard_dirname)
return [(fname, os.path.getsize(os.path.join(dname, fname)))
for fname in filenames]
except:
logging.exception("virtual_sdcard get_file_list")
raise self.gcode.error("Unable to get file list")
def get_status(self, eventtime):
progress = 0.
if self.work_timer is not None and self.file_size:
progress = float(self.file_position) / self.file_size
return {'progress': progress}
# G-Code commands
def cmd_error(self, params):
raise self.gcode.error("SD write not supported")
def cmd_M20(self, params):
# List SD card
files = self.get_file_list()
self.gcode.respond("Begin file list")
for fname, fsize in files:
self.gcode.respond("%s %d" % (fname, fsize))
self.gcode.respond("End file list")
def cmd_M21(self, params):
# Initialize SD card
self.gcode.respond("SD card ok")
def cmd_M23(self, params):
# Select SD file
if self.work_timer is not None:
raise self.gcode.error("SD busy")
if self.current_file is not None:
self.current_file.close()
self.current_file = None
self.file_position = self.file_size = 0
try:
orig = params['#original']
filename = orig[orig.find("M23") + 4:].split()[0].strip()
if '*' in filename:
filename = filename[:filename.find('*')].strip()
except:
raise self.gcode.error("Unable to extract filename")
if filename.startswith('/'):
filename = filename[1:]
files = self.get_file_list()
files_by_lower = { fname.lower(): fname for fname, fsize in files }
try:
fname = files_by_lower[filename.lower()]
fname = os.path.join(self.sdcard_dirname, fname)
f = open(fname, 'rb')
f.seek(0, os.SEEK_END)
fsize = f.tell()
f.seek(0)
except:
logging.exception("virtual_sdcard file open")
raise self.gcode.error("Unable to open file")
self.gcode.respond("File opened:%s Size:%d" % (filename, fsize))
self.gcode.respond("File selected")
self.current_file = f
self.file_position = 0
self.file_size = fsize
def cmd_M24(self, params):
# Start/resume SD print
if self.work_timer is not None:
raise self.gcode.error("SD busy")
self.must_pause_work = False
self.work_timer = self.reactor.register_timer(
self.work_handler, self.reactor.NOW)
def cmd_M25(self, params):
# Pause SD print
if self.work_timer is not None:
self.must_pause_work = True
def cmd_M26(self, params):
# Set SD position
if self.work_timer is not None:
raise self.gcode.error("SD busy")
pos = self.gcode.get_int('S', params)
self.file_position = pos
def cmd_M27(self, params):
# Report SD print status
if self.current_file is None or self.work_timer is None:
self.gcode.respond("Not SD printing.")
return
self.gcode.respond("SD printing byte %d/%d" % (
self.file_position, self.file_size))
# Background work timer
def work_handler(self, eventtime):
self.reactor.unregister_timer(self.work_timer)
try:
self.current_file.seek(self.file_position)
except:
logging.exception("virtual_sdcard seek")
self.gcode.respond_error("Unable to seek file")
self.work_timer = None
return self.reactor.NEVER
partial_input = ""
lines = []
while not self.must_pause_work:
if not lines:
# Read more data
try:
data = self.current_file.read(8192)
except:
logging.exception("virtual_sdcard read")
self.gcode.respond_error("Error on virtual sdcard read")
break
if not data:
# End of file
self.current_file.close()
self.current_file = None
self.gcode.respond("Done printing file")
break
lines = data.split('\n')
lines[0] = partial_input + lines[0]
partial_input = lines.pop()
lines.reverse()
continue
# Dispatch command
try:
res = self.gcode.process_batch(lines[-1])
if not res:
self.reactor.pause(self.reactor.monotonic() + 0.100)
continue
except self.gcode.error as e:
break
except:
logging.exception("virtual_sdcard dispatch")
break
self.file_position += len(lines.pop()) + 1
self.work_timer = None
return self.reactor.NEVER
def load_config(config):
return VirtualSD(config)

View File

@@ -1,6 +1,6 @@
# Code for handling printer nozzle extruders
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
@@ -10,9 +10,12 @@ EXTRUDE_DIFF_IGNORE = 1.02
class PrinterExtruder:
def __init__(self, printer, config):
self.config = config
shared_heater = config.get('shared_heater', None)
if shared_heater is None:
self.heater = heater.PrinterHeater(printer, config)
self.stepper = stepper.PrinterStepper(printer, config, 'extruder')
else:
self.heater = get_printer_heater(printer, shared_heater)
self.stepper = stepper.PrinterStepper(printer, config)
self.nozzle_diameter = config.getfloat('nozzle_diameter', above=0.)
filament_diameter = config.getfloat(
'filament_diameter', minval=self.nozzle_diameter)
@@ -21,12 +24,12 @@ class PrinterExtruder:
'max_extrude_cross_section', 4. * self.nozzle_diameter**2
, above=0.)
self.max_extrude_ratio = max_cross_section / self.filament_area
toolhead = printer.objects['toolhead']
toolhead = printer.lookup_object('toolhead')
max_velocity, max_accel = toolhead.get_max_velocity()
self.max_e_velocity = self.config.getfloat(
self.max_e_velocity = config.getfloat(
'max_extrude_only_velocity', max_velocity * self.max_extrude_ratio
, above=0.)
self.max_e_accel = self.config.getfloat(
self.max_e_accel = config.getfloat(
'max_extrude_only_accel', max_accel * self.max_extrude_ratio
, above=0.)
self.stepper.set_max_jerk(9999999.9, 9999999.9)
@@ -50,6 +53,8 @@ class PrinterExtruder:
if is_active:
return self.activate_gcode
return self.deactivate_gcode
def stats(self, eventtime):
return self.heater.stats(eventtime)
def motor_off(self, print_time):
self.stepper.motor_enable(print_time, 0)
self.need_motor_enable = True
@@ -70,8 +75,11 @@ class PrinterExtruder:
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):
elif move.extrude_r > self.max_extrude_ratio:
if move.axes_d[3] <= self.nozzle_diameter * self.max_extrude_ratio:
# Permit extrusion if amount extruded is tiny
move.extrude_r = self.max_extrude_ratio
return
area = move.axes_d[3] * self.filament_area / move.move_d
logging.debug("Overextrude: %s vs %s (area=%.3f dist=%.3f)",
move.extrude_r, self.max_extrude_ratio,
@@ -189,29 +197,27 @@ class PrinterExtruder:
decel_d -= extra_decel_d
# Prepare for steps
mcu_stepper = self.stepper.mcu_stepper
step_const = self.stepper.step_const
move_time = print_time
# Acceleration steps
if accel_d:
mcu_stepper.step_const(move_time, start_pos, accel_d, start_v, accel)
step_const(move_time, start_pos, accel_d, start_v, accel)
start_pos += accel_d
move_time += accel_t
# Cruising steps
if cruise_d:
mcu_stepper.step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
step_const(move_time, start_pos, cruise_d, cruise_v, 0.)
start_pos += cruise_d
move_time += cruise_t
# Deceleration steps
if decel_d:
mcu_stepper.step_const(
move_time, start_pos, decel_d, decel_v, -accel)
step_const(move_time, start_pos, decel_d, decel_v, -accel)
start_pos += decel_d
move_time += decel_t
# Retraction steps
if retract_d:
mcu_stepper.step_const(
move_time, start_pos, -retract_d, retract_v, accel)
step_const(move_time, start_pos, -retract_d, retract_v, accel)
start_pos -= retract_d
self.extrude_pos = start_pos
@@ -244,17 +250,17 @@ def add_printer_objects(printer, config):
def get_printer_extruders(printer):
out = []
for i in range(99):
extruder = printer.objects.get('extruder%d' % (i,))
extruder = printer.lookup_object('extruder%d' % (i,), None)
if extruder is None:
break
out.append(extruder)
return out
def get_printer_heater(printer, name):
if name == 'heater_bed' and name in printer.objects:
return printer.objects[name]
if name == 'heater_bed':
return printer.lookup_object(name)
if name == 'extruder':
name = 'extruder0'
if name.startswith('extruder') and name in printer.objects:
return printer.objects[name].get_heater()
if name.startswith('extruder'):
return printer.lookup_object(name).get_heater()
raise printer.config_error("Unknown heater '%s'" % (name,))

View File

@@ -1,63 +0,0 @@
# Printer fan support
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import extruder, pins
FAN_MIN_TIME = 0.1
PWM_CYCLE_TIME = 0.010
class PrinterFan:
def __init__(self, printer, config):
self.last_fan_value = 0.
self.last_fan_time = 0.
self.max_power = config.getfloat('max_power', 1., above=0., maxval=1.)
self.kick_start_time = config.getfloat('kick_start_time', 0.1, minval=0.)
self.mcu_fan = pins.setup_pin(printer, 'pwm', config.get('pin'))
self.mcu_fan.setup_max_duration(0.)
self.mcu_fan.setup_cycle_time(PWM_CYCLE_TIME)
self.mcu_fan.setup_hard_pwm(config.getint('hard_pwm', 0))
def set_speed(self, print_time, value):
value = max(0., min(self.max_power, value))
if value == self.last_fan_value:
return
print_time = max(self.last_fan_time + FAN_MIN_TIME, print_time)
if (value and value < self.max_power
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(print_time, self.max_power)
print_time += self.kick_start_time
self.mcu_fan.set_pwm(print_time, value)
self.last_fan_time = print_time
self.last_fan_value = value
class PrinterHeaterFan:
def __init__(self, printer, config):
self.fan = PrinterFan(printer, config)
self.mcu = printer.objects['mcu']
heater = config.get("heater", "extruder0")
self.heater = extruder.get_printer_heater(printer, heater)
self.heater_temp = config.getfloat("heater_temp", 50.0)
max_power = self.fan.max_power
self.fan_speed = config.getfloat(
"fan_speed", max_power, minval=0., maxval=max_power)
self.fan.mcu_fan.setup_shutdown_value(max_power)
printer.reactor.register_timer(self.callback, printer.reactor.NOW)
def callback(self, eventtime):
current_temp, target_temp = self.heater.get_temp(eventtime)
if not current_temp and not target_temp and not self.fan.last_fan_time:
# Printer still starting
return eventtime + 1.
power = 0.
if target_temp or current_temp > self.heater_temp:
power = self.fan_speed
print_time = self.mcu.estimated_print_time(eventtime) + FAN_MIN_TIME
self.fan.set_speed(print_time, power)
return eventtime + 1.
def add_printer_objects(printer, config):
if config.has_section('fan'):
printer.add_object('fan', PrinterFan(printer, config.getsection('fan')))
for s in config.get_prefix_sections('heater_fan '):
printer.add_object(s.section, PrinterHeaterFan(printer, s))

View File

@@ -1,95 +1,133 @@
# Parse gcode commands
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import os, re, logging, collections
import homing, extruder, chipmisc
import homing, extruder
# Parse out incoming GCode and find and translate head movements
class error(Exception):
pass
# Parse and handle G-Code commands
class GCodeParser:
error = error
RETRY_TIME = 0.100
def __init__(self, printer, fd):
self.printer = printer
self.fd = fd
# Input handling
self.reactor = printer.reactor
self.reactor = printer.get_reactor()
self.is_processing_data = False
self.is_fileinput = not not printer.get_start_args().get("debuginput")
self.fd_handle = None
if not self.is_fileinput:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
self.partial_input = ""
self.pending_commands = []
self.bytes_read = 0
self.input_log = collections.deque([], 50)
# Command handling
self.is_printer_ready = False
self.gcode_handlers = {}
self.build_handlers()
self.need_ack = False
self.toolhead = self.fan = self.extruder = None
self.heaters = []
self.speed = 25.0
self.base_gcode_handlers = self.gcode_handlers = {}
self.ready_gcode_handlers = {}
self.gcode_help = {}
for cmd in self.all_handlers:
func = getattr(self, 'cmd_' + cmd)
wnr = getattr(self, 'cmd_' + cmd + '_when_not_ready', False)
desc = getattr(self, 'cmd_' + cmd + '_help', None)
self.register_command(cmd, func, wnr, desc)
for a in getattr(self, 'cmd_' + cmd + '_aliases', []):
self.register_command(a, func, wnr)
# G-Code coordinate manipulation
self.absolutecoord = self.absoluteextrude = True
self.base_position = [0.0, 0.0, 0.0, 0.0]
self.last_position = [0.0, 0.0, 0.0, 0.0]
self.homing_add = [0.0, 0.0, 0.0, 0.0]
self.speed_factor = 1. / 60.
self.extrude_factor = 1.
self.move_transform = self.move_with_transform = None
self.position_with_transform = (lambda: [0., 0., 0., 0.])
# G-Code state
self.need_ack = False
self.toolhead = self.fan = self.extruder = None
self.heaters = []
self.speed = 25.0
self.axis2pos = {'X': 0, 'Y': 1, 'Z': 2, 'E': 3}
def build_handlers(self):
handlers = self.all_handlers
if not self.is_printer_ready:
handlers = [h for h in handlers
if getattr(self, 'cmd_'+h+'_when_not_ready', False)]
gcode_handlers = { h: getattr(self, 'cmd_'+h) for h in handlers }
for h, f in list(gcode_handlers.items()):
aliases = getattr(self, 'cmd_'+h+'_aliases', [])
gcode_handlers.update({ a: f for a in aliases })
self.gcode_handlers = gcode_handlers
def register_command(self, cmd, func, when_not_ready=False, desc=None):
if func is None:
if cmd in self.ready_gcode_handlers:
del self.ready_gcode_handlers[cmd]
if cmd in self.base_gcode_handlers:
del self.base_gcode_handlers[cmd]
return
if not (len(cmd) >= 2 and not cmd[0].isupper() and cmd[1].isdigit()):
origfunc = func
func = lambda params: origfunc(self.get_extended_params(params))
self.ready_gcode_handlers[cmd] = func
if when_not_ready:
self.base_gcode_handlers[cmd] = func
if desc is not None:
self.gcode_help[cmd] = desc
def set_move_transform(self, transform):
if self.move_transform is not None:
raise self.printer.config_error(
"G-Code move transform already specified")
self.move_transform = transform
self.move_with_transform = transform.move
self.position_with_transform = transform.get_position
def stats(self, eventtime):
return "gcodein=%d" % (self.bytes_read,)
def connect(self):
return False, "gcodein=%d" % (self.bytes_read,)
def get_status(self, eventtime):
busy = self.is_processing_data
return {'speed_factor': self.speed_factor * 60., 'busy': busy}
def printer_state(self, state):
if state == 'shutdown':
if not self.is_printer_ready:
return
self.is_printer_ready = False
self.gcode_handlers = self.base_gcode_handlers
self.dump_debug()
if self.is_fileinput:
self.printer.request_exit()
return
if state != 'ready':
return
self.is_printer_ready = True
self.build_handlers()
self.gcode_handlers = self.ready_gcode_handlers
# Lookup printer components
self.toolhead = self.printer.objects.get('toolhead')
self.toolhead = self.printer.lookup_object('toolhead')
if self.move_transform is None:
self.move_with_transform = self.toolhead.move
self.position_with_transform = self.toolhead.get_position
extruders = extruder.get_printer_extruders(self.printer)
if extruders:
self.extruder = extruders[0]
self.toolhead.set_extruder(self.extruder)
self.heaters = [ e.get_heater() for e in extruders ]
self.heaters.append(self.printer.objects.get('heater_bed'))
self.fan = self.printer.objects.get('fan')
self.heaters.append(self.printer.lookup_object('heater_bed', None))
self.fan = self.printer.lookup_object('fan', None)
if self.is_fileinput and self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
def do_shutdown(self):
if not self.is_printer_ready:
return
self.is_printer_ready = False
self.build_handlers()
self.dump_debug()
if self.is_fileinput:
self.printer.request_exit()
def motor_heater_off(self):
if self.toolhead is None:
return
self.toolhead.motor_off()
print_time = self.toolhead.get_last_move_time()
for heater in self.heaters:
if heater is not None:
heater.set_temp(print_time, 0.)
if self.fan is not None:
self.fan.set_speed(print_time, 0.)
def reset_last_position(self):
self.last_position = self.position_with_transform()
def dump_debug(self):
out = []
out.append("Dumping gcode input %d blocks" % (
len(self.input_log),))
for eventtime, data in self.input_log:
out.append("Read %f: %s" % (eventtime, repr(data)))
out.append(
"gcode state: absolutecoord=%s absoluteextrude=%s"
" base_position=%s last_position=%s homing_add=%s"
" speed_factor=%s extrude_factor=%s speed=%s" % (
self.absolutecoord, self.absoluteextrude,
self.base_position, self.last_position, self.homing_add,
self.speed_factor, self.extrude_factor, self.speed))
logging.info("\n".join(out))
# Parse input into commands
args_r = re.compile('([A-Z_]+|[A-Z*])')
args_r = re.compile('([A-Z_]+|[A-Z*/])')
def process_commands(self, commands, need_ack=True):
prev_need_ack = self.need_ack
for line in commands:
# Ignore comments and leading/trailing spaces
line = origline = line.strip()
@@ -105,8 +143,8 @@ class GCodeParser:
# Skip line number at start of command
del parts[:2]
if not parts:
self.cmd_default(params)
continue
# Treat empty line as empty command
parts = ['', '']
params['#command'] = cmd = parts[0] + parts[1].strip()
# Invoke handler for command
self.need_ack = need_ack
@@ -115,38 +153,78 @@ class GCodeParser:
handler(params)
except error as e:
self.respond_error(str(e))
self.reset_last_position()
if not need_ack:
raise
except:
msg = 'Internal error on command:"%s"' % (cmd,)
logging.exception(msg)
self.printer.invoke_shutdown(msg)
self.respond_error(msg)
if not need_ack:
raise
self.ack()
self.need_ack = prev_need_ack
m112_r = re.compile('^(?:[nN][0-9]+)?\s*[mM]112(?:\s|$)')
def process_data(self, eventtime):
# Read input, separate by newline, and add to pending_commands
data = os.read(self.fd, 4096)
self.input_log.append((eventtime, data))
self.bytes_read += len(data)
lines = data.split('\n')
lines[0] = self.partial_input + lines[0]
self.partial_input = lines.pop()
pending_commands = self.pending_commands
pending_commands.extend(lines)
# Special handling for debug file input EOF
if not data and self.is_fileinput:
if not self.is_processing_data:
self.request_restart('exit')
pending_commands.append("")
# Handle case where multiple commands pending
if self.is_processing_data or len(pending_commands) > 1:
if len(pending_commands) < 20:
# Check for M112 out-of-order
for line in lines:
if self.m112_r.match(line) is not None:
self.cmd_M112({})
if self.is_processing_data:
if not self.is_fileinput and not lines:
return
if len(pending_commands) >= 20:
# Stop reading input
self.reactor.unregister_fd(self.fd_handle)
self.fd_handle = None
if not self.is_fileinput and lines[0].strip().upper() == 'M112':
self.cmd_M112({})
while self.is_processing_data:
eventtime = self.reactor.pause(eventtime + 0.100)
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
return
# Process commands
self.is_processing_data = True
self.process_commands(lines)
if not data and self.is_fileinput:
self.motor_heater_off()
if self.toolhead is not None:
self.toolhead.wait_moves()
self.printer.request_exit()
self.pending_commands = []
self.process_commands(pending_commands)
if self.pending_commands:
self.process_pending()
self.is_processing_data = False
def process_pending(self):
pending_commands = self.pending_commands
while pending_commands:
self.pending_commands = []
self.process_commands(pending_commands)
pending_commands = self.pending_commands
if self.fd_handle is None:
self.fd_handle = self.reactor.register_fd(self.fd, self.process_data)
def process_batch(self, command):
if self.is_processing_data:
return False
self.is_processing_data = True
try:
self.process_commands([command], need_ack=False)
finally:
if self.pending_commands:
self.process_pending()
self.is_processing_data = False
return True
def run_script(self, script):
prev_need_ack = self.need_ack
try:
self.process_commands(script.split('\n'), need_ack=False)
finally:
self.need_ack = prev_need_ack
# Response handling
def ack(self, msg=None):
if not self.need_ack or self.is_fileinput:
@@ -168,29 +246,24 @@ class GCodeParser:
logging.warning(msg)
lines = msg.strip().split('\n')
if len(lines) > 1:
self.respond_info("\n".join(lines[:-1]))
self.respond('!! %s' % (lines[-1].strip(),))
self.respond_info("\n".join(lines))
self.respond('!! %s' % (lines[0].strip(),))
# Parameter parsing helpers
def get_int(self, name, params, default=None):
class sentinel: pass
def get_str(self, name, params, default=sentinel, parser=str):
if name in params:
try:
return int(params[name])
except ValueError:
return parser(params[name])
except:
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:
if default is not self.sentinel:
return default
raise error("Error on '%s': missing %s" % (params['#original'], name))
def get_int(self, name, params, default=sentinel):
return self.get_str(name, params, default, parser=int)
def get_float(self, name, params, default=sentinel):
return self.get_str(name, params, default, parser=float)
extended_r = re.compile(
r'^\s*(?:N[0-9]+\s*)?'
r'(?P<cmd>[a-zA-Z_][a-zA-Z_]+)(?:\s+|$)'
@@ -250,9 +323,8 @@ class GCodeParser:
try:
heater.set_temp(print_time, temp)
except heater.error as e:
self.respond_error(str(e))
return
if wait:
raise error(str(e))
if wait and temp:
self.bg_temp(heater)
def set_fan_speed(self, speed):
if self.fan is None:
@@ -261,7 +333,7 @@ class GCodeParser:
return
print_time = self.toolhead.get_last_move_time()
self.fan.set_speed(print_time, speed)
# Individual command handlers
# G-Code special command handlers
def cmd_default(self, params):
if not self.is_printer_ready:
self.respond_error(self.printer.get_state_message())
@@ -285,50 +357,54 @@ class GCodeParser:
e = extruders[index]
if self.extruder is e:
return
deactivate_gcode = self.extruder.get_activate_gcode(False)
self.process_commands(deactivate_gcode.split('\n'), need_ack=False)
self.run_script(self.extruder.get_activate_gcode(False))
try:
self.toolhead.set_extruder(e)
except homing.EndstopError as e:
self.respond_error(str(e))
return
raise error(str(e))
self.extruder = e
self.last_position = self.toolhead.get_position()
activate_gcode = self.extruder.get_activate_gcode(True)
self.process_commands(activate_gcode.split('\n'), need_ack=False)
self.reset_last_position()
self.run_script(self.extruder.get_activate_gcode(True))
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', 'SET_SERVO',
'G1', 'G4', 'G28', 'M18', 'M400',
'G20', 'M82', 'M83', 'G90', 'G91', 'G92', 'M114', 'M206', 'M220', 'M221',
'M105', 'M104', 'M109', 'M140', 'M190', 'M106', 'M107',
'M112', 'M115', 'IGNORE', 'QUERY_ENDSTOPS', 'GET_POSITION',
'RESTART', 'FIRMWARE_RESTART', 'ECHO', 'STATUS', 'HELP']
# G-Code movement commands
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)):
for axis in 'XYZ':
if axis in params:
v = float(params[axis])
pos = self.axis2pos[axis]
if not self.absolutecoord:
# value relative to position of last move
self.last_position[p] += v
self.last_position[pos] += v
else:
# value relative to base coordinate position
self.last_position[p] = v + self.base_position[p]
self.last_position[pos] = v + self.base_position[pos]
if 'E' in params:
v = float(params['E']) * self.extrude_factor
if not self.absolutecoord or not self.absoluteextrude:
# value relative to position of last move
self.last_position[3] += v
else:
# value relative to base coordinate position
self.last_position[3] = v + self.base_position[3]
if 'F' in params:
speed = float(params['F']) / 60.
speed = float(params['F']) * self.speed_factor
if speed <= 0.:
raise ValueError()
raise error("Invalid speed in '%s'" % (params['#original'],))
self.speed = speed
except ValueError as 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)
self.move_with_transform(self.last_position, self.speed)
except homing.EndstopError as e:
self.respond_error(str(e))
self.last_position = self.toolhead.get_position()
raise error(str(e))
def cmd_G4(self, params):
# Dwell
if 'S' in params:
@@ -336,9 +412,6 @@ class GCodeParser:
else:
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_G28(self, params):
# Move to origin
axes = []
@@ -347,19 +420,33 @@ class GCodeParser:
axes.append(self.axis2pos[axis])
if not axes:
axes = [0, 1, 2]
homing_state = homing.Homing(self.toolhead, axes)
homing_state = homing.Homing(self.toolhead)
if self.is_fileinput:
homing_state.set_no_verify_retract()
try:
self.toolhead.home(homing_state)
homing_state.home_axes(axes)
except homing.EndstopError as e:
self.toolhead.motor_off()
self.respond_error(str(e))
return
newpos = self.toolhead.get_position()
raise error(str(e))
for axis in homing_state.get_axes():
self.last_position[axis] = newpos[axis]
self.base_position[axis] = -self.homing_add[axis]
self.reset_last_position()
cmd_M18_aliases = ["M84"]
def cmd_M18(self, params):
# Turn off motors
self.toolhead.motor_off()
def cmd_M400(self, params):
# Wait for current moves to finish
self.toolhead.wait_moves()
# G-Code coordinate manipulation
def cmd_G20(self, params):
# Set units to inches
self.respond_error('Machine does not support G20 (inches) command')
def cmd_M82(self, params):
# Use absolute distances for extrusion
self.absoluteextrude = True
def cmd_M83(self, params):
# Use relative distances for extrusion
self.absoluteextrude = False
def cmd_G90(self, params):
# Use absolute coordinates
self.absolutecoord = True
@@ -371,19 +458,40 @@ class GCodeParser:
offsets = { p: self.get_float(a, params)
for a, p in self.axis2pos.items() if a in params }
for p, offset in offsets.items():
if p == 3:
offset *= self.extrude_factor
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
self.absoluteextrude = True
def cmd_M83(self, params):
# Use relative distances for extrusion
self.absoluteextrude = False
cmd_M18_aliases = ["M84"]
def cmd_M18(self, params):
# Turn off motors
self.toolhead.motor_off()
cmd_M114_when_not_ready = True
def cmd_M114(self, params):
# Get Current Position
p = [lp - bp for lp, bp in zip(self.last_position, self.base_position)]
p[3] /= self.extrude_factor
self.respond("X:%.3f Y:%.3f Z:%.3f E:%.3f" % tuple(p))
def cmd_M206(self, params):
# Set home offset
offsets = { self.axis2pos[a]: self.get_float(a, params)
for a in 'XYZ' 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_M220(self, params):
# Set speed factor override percentage
value = self.get_float('S', params, 100.) / (60. * 100.)
if value <= 0.:
raise error("Invalid factor in '%s'" % (params['#original'],))
self.speed_factor = value
def cmd_M221(self, params):
# Set extrude factor override percentage
new_extrude_factor = self.get_float('S', params, 100.) / 100.
if new_extrude_factor <= 0.:
raise error("Invalid factor in '%s'" % (params['#original'],))
last_e_pos = self.last_position[3]
e_value = (last_e_pos - self.base_position[3]) / self.extrude_factor
self.base_position[3] = last_e_pos - e_value * new_extrude_factor
self.extrude_factor = new_extrude_factor
# G-Code temperature and fan commands
cmd_M105_when_not_ready = True
def cmd_M105(self, params):
# Get Extruder Temperature
@@ -394,26 +502,6 @@ class GCodeParser:
def cmd_M109(self, params):
# Set Extruder Temperature and Wait
self.set_temp(params, wait=True)
def cmd_M112(self, params):
# Emergency Stop
self.printer.invoke_shutdown("Shutdown due to M112 command")
cmd_M114_when_not_ready = True
def cmd_M114(self, params):
# Get Current Position
if self.toolhead is None:
self.cmd_default(params)
return
raw_pos = self.toolhead.query_endstops("get_mcu_position")
self.respond("X:%.3f Y:%.3f Z:%.3f E:%.3f Count %s" % (
self.last_position[0], self.last_position[1],
self.last_position[2], self.last_position[3],
" ".join(["%s:%d" % (n.upper(), p) for n, p in raw_pos])))
cmd_M115_when_not_ready = True
def cmd_M115(self, params):
# Get Firmware Version and Capabilities
software_version = self.printer.get_start_args().get('software_version')
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": 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(params, is_bed=True)
@@ -426,16 +514,17 @@ class GCodeParser:
def cmd_M107(self, params):
# Turn fan off
self.set_fan_speed(0.)
def cmd_M206(self, params):
# Set home offset
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()
# G-Code miscellaneous commands
cmd_M112_when_not_ready = True
def cmd_M112(self, params):
# Emergency Stop
self.printer.invoke_shutdown("Shutdown due to M112 command")
cmd_M115_when_not_ready = True
def cmd_M115(self, params):
# Get Firmware Version and Capabilities
software_version = self.printer.get_start_args().get('software_version')
kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version}
self.ack(" ".join(["%s:%s" % (k, v) for k, v in kw.items()]))
cmd_IGNORE_when_not_ready = True
cmd_IGNORE_aliases = ["G21", "M110", "M21"]
def cmd_IGNORE(self, params):
@@ -445,57 +534,62 @@ class GCodeParser:
cmd_QUERY_ENDSTOPS_aliases = ["M119"]
def cmd_QUERY_ENDSTOPS(self, params):
# Get Endstop Status
if self.is_fileinput:
return
try:
res = self.toolhead.query_endstops()
except homing.EndstopError as e:
self.respond_error(str(e))
return
res = homing.query_endstops(self.toolhead)
self.respond(" ".join(["%s:%s" % (name, ["open", "TRIGGERED"][not not t])
for name, t in res]))
cmd_PID_TUNE_help = "Run PID Tuning"
cmd_PID_TUNE_aliases = ["M303"]
def cmd_PID_TUNE(self, params):
# Run PID tuning
heater_index = self.get_int('E', params, 0)
if (heater_index < -1 or heater_index >= len(self.heaters) - 1
or self.heaters[heater_index] is None):
self.respond_error("Heater not configured")
heater = self.heaters[heater_index]
temp = self.get_float('S', params)
heater.start_auto_tune(temp)
self.bg_temp(heater)
cmd_SET_SERVO_help = "Set servo angle"
def cmd_SET_SERVO(self, params):
params = self.get_extended_params(params)
name = params.get('SERVO')
if name is None:
raise error("Error on '%s': missing SERVO" % (params['#original'],))
s = chipmisc.get_printer_servo(self.printer, name)
if s is None:
raise error("Servo not configured")
print_time = self.toolhead.get_last_move_time()
if 'WIDTH' in params:
s.set_pulse_width(print_time, self.get_float('WIDTH', params))
cmd_GET_POSITION_when_not_ready = True
def cmd_GET_POSITION(self, params):
if self.toolhead is None:
self.cmd_default(params)
return
s.set_angle(print_time, self.get_float('ANGLE', params))
def prep_restart(self):
kin = self.toolhead.get_kinematics()
steppers = kin.get_steppers()
mcu_pos = " ".join(["%s:%d" % (s.name, s.mcu_stepper.get_mcu_position())
for s in steppers])
stepper_pos = " ".join(
["%s:%.6f" % (s.name, s.mcu_stepper.get_commanded_position())
for s in steppers])
kinematic_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZE", kin.get_position())])
toolhead_pos = " ".join(["%s:%.6f" % (a, v) for a, v in zip(
"XYZE", self.toolhead.get_position())])
gcode_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZE", self.last_position)])
origin_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZE", self.base_position)])
homing_pos = " ".join(["%s:%.6f" % (a, v)
for a, v in zip("XYZE", self.homing_add)])
self.respond_info(
"mcu: %s\n"
"stepper: %s\n"
"kinematic: %s\n"
"toolhead: %s\n"
"gcode: %s\n"
"gcode origin: %s\n"
"gcode homing: %s" % (
mcu_pos, stepper_pos, kinematic_pos, toolhead_pos,
gcode_pos, origin_pos, homing_pos))
def request_restart(self, result):
if self.is_printer_ready:
self.respond_info("Preparing to restart...")
self.motor_heater_off()
self.toolhead.motor_off()
print_time = self.toolhead.get_last_move_time()
for heater in self.heaters:
if heater is not None:
heater.set_temp(print_time, 0.)
if self.fan is not None:
self.fan.set_speed(print_time, 0.)
self.toolhead.dwell(0.500)
self.toolhead.wait_moves()
self.printer.request_exit(result)
cmd_RESTART_when_not_ready = True
cmd_RESTART_help = "Reload config file and restart host software"
def cmd_RESTART(self, params):
self.prep_restart()
self.printer.request_exit('restart')
self.request_restart('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')
self.request_restart('firmware_restart')
cmd_ECHO_when_not_ready = True
def cmd_ECHO(self, params):
self.respond_info(params['#original'])
@@ -514,10 +608,6 @@ class GCodeParser:
cmdhelp.append("Printer is not ready - not all commands available.")
cmdhelp.append("Available extended commands:")
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))
if cmd in self.gcode_help:
cmdhelp.append("%-10s: %s" % (cmd, self.gcode_help[cmd]))
self.respond_info("\n".join(cmdhelp))
class error(Exception):
pass

View File

@@ -1,6 +1,6 @@
# Printer heater support
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging, threading
@@ -17,7 +17,8 @@ KELVIN_TO_CELCIUS = -273.15
class Thermistor:
def __init__(self, config, params):
self.pullup = config.getfloat('pullup_resistor', 4700., above=0.)
# Calculate Steinhart-Hart coefficents from temp measurements
# Calculate Steinhart-Hart coefficents from temp measurements.
# Arrange samples as 3 linear equations and solve for c1, c2, and c3.
inv_t1 = 1. / (params['t1'] - KELVIN_TO_CELCIUS)
inv_t2 = 1. / (params['t2'] - KELVIN_TO_CELCIUS)
inv_t3 = 1. / (params['t3'] - KELVIN_TO_CELCIUS)
@@ -35,6 +36,7 @@ class Thermistor:
self.c2 = (inv_t12 - self.c3 * ln3_r12) / ln_r12
self.c1 = inv_t1 - self.c2 * ln_r1 - self.c3 * ln3_r1
def calc_temp(self, adc):
adc = max(.00001, min(.99999, adc))
r = self.pullup * adc / (1.0 - adc)
ln_r = math.log(r)
inv_t = self.c1 + self.c2 * ln_r + self.c3 * ln_r**3
@@ -42,6 +44,7 @@ class Thermistor:
def calc_adc(self, temp):
inv_t = 1. / (temp - KELVIN_TO_CELCIUS)
if self.c3:
# Solve for ln_r using Cardano's formula
y = (self.c1 - inv_t) / (2. * self.c3)
x = math.sqrt((self.c2 / (3. * self.c3))**3 + y**2)
ln_r = math.pow(x - y, 1./3.) - math.pow(x + y, 1./3.)
@@ -94,10 +97,10 @@ Sensors = {
SAMPLE_TIME = 0.001
SAMPLE_COUNT = 8
REPORT_TIME = 0.300
PWM_CYCLE_TIME = 0.100
MAX_HEAT_TIME = 5.0
AMBIENT_TEMP = 25.
PID_PARAM_BASE = 255.
PWM_DELAY = REPORT_TIME + SAMPLE_TIME*SAMPLE_COUNT
class error(Exception):
pass
@@ -105,10 +108,11 @@ class error(Exception):
class PrinterHeater:
error = error
def __init__(self, printer, config):
self.name = config.section
self.printer = printer
self.name = config.get_name()
sensor_params = config.getchoice('sensor_type', Sensors)
self.sensor = sensor_params['class'](config, sensor_params)
self.min_temp = config.getfloat('min_temp', minval=0.)
self.min_temp = config.getfloat('min_temp', minval=KELVIN_TO_CELCIUS)
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)
@@ -124,7 +128,9 @@ class PrinterHeater:
self.mcu_pwm = pins.setup_pin(printer, 'digital_out', heater_pin)
else:
self.mcu_pwm = pins.setup_pin(printer, 'pwm', heater_pin)
self.mcu_pwm.setup_cycle_time(PWM_CYCLE_TIME)
pwm_cycle_time = config.getfloat(
'pwm_cycle_time', 0.100, above=0., maxval=REPORT_TIME)
self.mcu_pwm.setup_cycle_time(pwm_cycle_time)
self.mcu_pwm.setup_max_duration(MAX_HEAT_TIME)
self.mcu_adc = pins.setup_pin(printer, 'adc', config.get('sensor_pin'))
adc_range = [self.sensor.calc_adc(self.min_temp),
@@ -137,7 +143,10 @@ class PrinterHeater:
self.control = algo(self, config)
# pwm caching
self.next_pwm_time = 0.
self.last_pwm_value = 0
self.last_pwm_value = 0.
# Load additional modules
printer.try_load_module(config, "verify_heater %s" % (self.name,))
printer.try_load_module(config, "pid_calibrate")
def set_pwm(self, read_time, value):
if self.target_temp <= 0.:
value = 0.
@@ -145,7 +154,7 @@ class PrinterHeater:
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
pwm_time = read_time + PWM_DELAY
self.next_pwm_time = pwm_time + 0.75 * MAX_HEAT_TIME
self.last_pwm_value = value
logging.debug("%s: pwm=%.3f@%.3f (from %.3f@%.3f [%.3f])",
@@ -176,16 +185,25 @@ class PrinterHeater:
def check_busy(self, eventtime):
with self.lock:
return self.control.check_busy(eventtime)
def start_auto_tune(self, 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))
def set_control(self, control):
with self.lock:
self.control = ControlAutoTune(self, self.control)
self.target_temp = degrees
def finish_auto_tune(self, old_control):
self.control = old_control
self.target_temp = 0
old_control = self.control
self.control = control
self.target_temp = 0.
return old_control
def stats(self, eventtime):
with self.lock:
target_temp = self.target_temp
last_temp = self.last_temp
last_pwm_value = self.last_pwm_value
is_active = target_temp or last_temp > 50.
return is_active, '%s: target=%.0f temp=%.1f pwm=%.3f' % (
self.name, target_temp, last_temp, last_pwm_value)
def get_status(self, eventtime):
with self.lock:
target_temp = self.target_temp
last_temp = self.last_temp
return {'temperature': last_temp, 'target': target_temp}
######################################################################
@@ -214,6 +232,9 @@ class ControlBangBang:
# Proportional Integral Derivative (PID) control algo
######################################################################
PID_SETTLE_DELTA = 1.
PID_SETTLE_SLOPE = .1
class ControlPID:
def __init__(self, heater, config):
self.heater = heater
@@ -254,112 +275,8 @@ class ControlPID:
self.prev_temp_integ = temp_integ
def check_busy(self, eventtime):
temp_diff = self.heater.target_temp - self.heater.last_temp
return abs(temp_diff) > 1. or abs(self.prev_temp_deriv) > 0.1
######################################################################
# Ziegler-Nichols PID autotuning
######################################################################
TUNE_PID_DELTA = 5.0
class ControlAutoTune:
def __init__(self, heater, old_control):
self.heater = heater
self.old_control = old_control
self.heating = False
self.peaks = []
self.peak = 0.
self.peak_time = 0.
def adc_callback(self, read_time, temp):
if self.heating and temp >= self.heater.target_temp:
self.heating = False
self.check_peaks()
elif (not self.heating
and temp <= self.heater.target_temp - TUNE_PID_DELTA):
self.heating = True
self.check_peaks()
if self.heating:
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.)
if temp > self.peak:
self.peak = temp
self.peak_time = read_time
def check_peaks(self):
self.peaks.append((self.peak, self.peak_time))
if self.heating:
self.peak = 9999999.
else:
self.peak = -9999999.
if len(self.peaks) < 4:
return
temp_diff = self.peaks[-1][0] - self.peaks[-2][0]
time_diff = self.peaks[-1][1] - self.peaks[-3][1]
max_power = self.heater.max_power
Ku = 4. * (2. * max_power) / (abs(temp_diff) * math.pi)
Tu = time_diff
Kp = 0.6 * Ku
Ti = 0.5 * Tu
Td = 0.125 * Tu
Ki = Kp / Ti
Kd = Kp * Td
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
self.heater.finish_auto_tune(self.old_control)
return False
######################################################################
# Tuning information test
######################################################################
class ControlBumpTest:
def __init__(self, heater, old_control):
self.heater = heater
self.old_control = old_control
self.temp_samples = {}
self.pwm_samples = {}
self.state = 0
def set_pwm(self, read_time, value):
self.pwm_samples[read_time + 2*REPORT_TIME] = value
self.heater.set_pwm(read_time, value)
def adc_callback(self, read_time, temp):
self.temp_samples[read_time] = temp
if not self.state:
self.set_pwm(read_time, 0.)
if len(self.temp_samples) >= 20:
self.state += 1
elif self.state == 1:
if temp < self.heater.target_temp:
self.set_pwm(read_time, self.heater.max_power)
return
self.set_pwm(read_time, 0.)
self.state += 1
elif self.state == 2:
self.set_pwm(read_time, 0.)
if temp <= (self.heater.target_temp + AMBIENT_TEMP) / 2.:
self.dump_stats()
self.state += 1
def dump_stats(self):
out = ["%.3f %.1f %d" % (time, temp, self.pwm_samples.get(time, -1.))
for time, temp in sorted(self.temp_samples.items())]
f = open("/tmp/heattest.txt", "wb")
f.write('\n'.join(out))
f.close()
def check_busy(self, eventtime):
if self.state < 3:
return True
self.heater.finish_auto_tune(self.old_control)
return False
return (abs(temp_diff) > PID_SETTLE_DELTA
or abs(self.prev_temp_deriv) > PID_SETTLE_SLOPE)
def add_printer_objects(printer, config):
if config.has_section('heater_bed'):

View File

@@ -3,16 +3,16 @@
# 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
import logging, math
HOMING_DELAY = 0.250
HOMING_STEP_DELAY = 0.00000025
ENDSTOP_SAMPLE_TIME = .000015
ENDSTOP_SAMPLE_COUNT = 4
class Homing:
def __init__(self, toolhead, changed_axes):
def __init__(self, toolhead):
self.toolhead = toolhead
self.changed_axes = changed_axes
self.changed_axes = []
self.verify_retract = True
def set_no_verify_retract(self):
self.verify_retract = False
@@ -29,50 +29,98 @@ class Homing:
return thcoord
def retract(self, newpos, speed):
self.toolhead.move(self._fill_coord(newpos), speed)
def home(self, forcepos, movepos, steppers, speed, second_home=False):
# Alter kinematics class to think printer is at forcepos
self.toolhead.set_position(self._fill_coord(forcepos))
# Start homing and issue move
if not second_home:
self.toolhead.dwell(HOMING_DELAY)
print_time = self.toolhead.get_last_move_time()
endstops = []
for s in steppers:
s.mcu_endstop.home_start(print_time, ENDSTOP_SAMPLE_TIME,
ENDSTOP_SAMPLE_COUNT, s.step_dist / speed)
endstops.append((s, s.mcu_stepper.get_mcu_position()))
self.toolhead.move(self._fill_coord(movepos), speed)
move_end_print_time = self.toolhead.get_last_move_time()
self.toolhead.reset_print_time(print_time)
for s, last_pos in endstops:
s.mcu_endstop.home_finalize(move_end_print_time)
# Wait for endstops to trigger
for s, last_pos in endstops:
try:
s.mcu_endstop.home_wait()
except s.mcu_endstop.error as e:
raise EndstopError("Failed to home stepper %s: %s" % (
s.name, str(e)))
post_home_pos = s.mcu_stepper.get_mcu_position()
if second_home and self.verify_retract and last_pos == post_home_pos:
raise EndstopError("Endstop %s still triggered after retract" % (
s.name,))
def set_homed_position(self, pos):
self.toolhead.set_position(self._fill_coord(pos))
def _get_homing_speed(self, speed, endstops):
# Round the requested homing speed so that it is an even
# number of ticks per step.
speed = min(speed, self.toolhead.get_max_velocity()[0])
mcu_stepper = endstops[0][0].get_steppers()[0]
adjusted_freq = mcu_stepper.get_mcu().get_adjusted_freq()
dist_ticks = adjusted_freq * mcu_stepper.get_step_dist()
ticks_per_step = math.ceil(dist_ticks / speed)
return dist_ticks / ticks_per_step
def homing_move(self, movepos, endstops, speed, probe_pos=False):
# Start endstop checking
for mcu_endstop, name in endstops:
mcu_endstop.home_prepare()
print_time = self.toolhead.get_last_move_time()
for mcu_endstop, name in endstops:
min_step_dist = min([s.get_step_dist()
for s in mcu_endstop.get_steppers()])
mcu_endstop.home_start(
print_time, ENDSTOP_SAMPLE_TIME, ENDSTOP_SAMPLE_COUNT,
min_step_dist / speed)
# Issue move
movepos = self._fill_coord(movepos)
error = None
try:
self.toolhead.move(movepos, speed)
except EndstopError as e:
error = "Error during homing move: %s" % (str(e),)
# Wait for endstops to trigger
move_end_print_time = self.toolhead.get_last_move_time()
self.toolhead.reset_print_time(print_time)
for mcu_endstop, name in endstops:
try:
mcu_endstop.home_wait(move_end_print_time)
except mcu_endstop.TimeoutError as e:
if error is None:
error = "Failed to home %s: %s" % (name, str(e))
if probe_pos:
self.set_homed_position(
list(self.toolhead.get_kinematics().get_position()) + [None])
else:
self.toolhead.set_position(movepos)
for mcu_endstop, name in endstops:
mcu_endstop.home_finalize()
if error is not None:
raise EndstopError(error)
def home(self, forcepos, movepos, endstops, speed, second_home=False):
if second_home and forcepos == movepos:
return
# Alter kinematics class to think printer is at forcepos
homing_axes = [axis for axis in range(3) if forcepos[axis] is not None]
self.toolhead.set_position(
self._fill_coord(forcepos), homing_axes=homing_axes)
# Add a CPU delay when homing a large axis
if not second_home:
est_move_d = sum([abs(forcepos[i]-movepos[i])
for i in range(3) if movepos[i] is not None])
est_steps = sum([est_move_d / s.get_step_dist()
for es, n in endstops for s in es.get_steppers()])
self.toolhead.dwell(est_steps * HOMING_STEP_DELAY, check_stall=False)
speed = self._get_homing_speed(speed, endstops)
# Setup for retract verification
self.toolhead.get_last_move_time()
start_mcu_pos = [(s, name, s.get_mcu_position())
for es, name in endstops for s in es.get_steppers()]
# Issue homing move
self.homing_move(movepos, endstops, speed)
# Verify retract led to some movement on second home
if second_home and self.verify_retract:
for s, name, pos in start_mcu_pos:
if s.get_mcu_position() == pos:
raise EndstopError(
"Endstop %s still triggered after retract" % (name,))
def home_axes(self, axes):
self.changed_axes = axes
try:
self.toolhead.get_kinematics().home(self)
except EndstopError:
self.toolhead.motor_off()
raise
def query_endstops(print_time, query_flags, steppers):
if query_flags == "get_mcu_position":
# Only the commanded position is requested
return [(s.name.upper(), s.mcu_stepper.get_mcu_position())
for s in steppers]
for s in steppers:
s.mcu_endstop.query_endstop(print_time)
def query_endstops(toolhead):
print_time = toolhead.get_last_move_time()
steppers = toolhead.get_kinematics().get_steppers()
out = []
for s in steppers:
try:
out.append((s.name, s.mcu_endstop.query_endstop_wait()))
except s.mcu_endstop.error as e:
raise EndstopError(str(e))
for mcu_endstop, name in s.get_endstops():
mcu_endstop.query_endstop(print_time)
for s in steppers:
for mcu_endstop, name in s.get_endstops():
out.append((name, mcu_endstop.query_endstop_wait()))
return out
class EndstopError(Exception):

View File

@@ -1,19 +1,20 @@
#!/usr/bin/env python2
# Main code for host side printer firmware
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, ConfigParser, logging, time, threading
import util, reactor, queuelogger, msgproto, gcode
import pins, mcu, chipmisc, toolhead, extruder, heater, fan
import sys, os, optparse, logging, time, threading
import collections, ConfigParser, importlib
import util, reactor, queuelogger, msgproto
import gcode, pins, mcu, toolhead, extruder, heater
message_ready = "Printer is ready"
message_startup = """
Printer is not ready
The klippy host software is attempting to connect. Please
retry in a few moments.
Printer is not ready
"""
message_restart = """
@@ -49,13 +50,18 @@ class ConfigWrapper:
error = ConfigParser.Error
class sentinel:
pass
def __init__(self, printer, section):
def __init__(self, printer, fileconfig, section):
self.printer = printer
self.fileconfig = fileconfig
self.section = section
def get_wrapper(self, parser, option, default
, minval=None, maxval=None, above=None, below=None):
def get_printer(self):
return self.printer
def get_name(self):
return self.section
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)):
and not self.fileconfig.has_option(self.section, option)):
return default
self.printer.all_config_options[
(self.section.lower(), option.lower())] = 1
@@ -84,18 +90,16 @@ class ConfigWrapper:
option, self.section, below))
return v
def get(self, option, default=sentinel):
return self.get_wrapper(self.printer.fileconfig.get, option, default)
return self._get_wrapper(self.fileconfig.get, option, default)
def getint(self, option, default=sentinel, minval=None, maxval=None):
return self.get_wrapper(
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)
return self._get_wrapper(
self.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.fileconfig.getfloat, option, default,
minval, maxval, above, below)
def getboolean(self, option, default=sentinel):
return self.get_wrapper(
self.printer.fileconfig.getboolean, option, default)
return self._get_wrapper(self.fileconfig.getboolean, option, default)
def getchoice(self, option, choices, default=sentinel):
c = self.get(option, default)
if c not in choices:
@@ -104,11 +108,11 @@ class ConfigWrapper:
option, self.section))
return choices[c]
def getsection(self, section):
return ConfigWrapper(self.printer, section)
return ConfigWrapper(self.printer, self.fileconfig, section)
def has_section(self, section):
return self.printer.fileconfig.has_section(section)
return self.fileconfig.has_section(section)
def get_prefix_sections(self, prefix):
return [self.getsection(s) for s in self.printer.fileconfig.sections()
return [self.getsection(s) for s in self.fileconfig.sections()
if s.startswith(prefix)]
class ConfigLogger():
@@ -130,8 +134,8 @@ class Printer:
if bglogger is not None:
bglogger.set_rollover_info("config", None)
self.reactor = reactor.Reactor()
self.objects = {}
self.gcode = gcode.GCodeParser(self, input_fd)
gc = gcode.GCodeParser(self, input_fd)
self.objects = collections.OrderedDict({'gcode': gc})
self.stats_timer = self.reactor.register_timer(self._stats)
self.connect_timer = self.reactor.register_timer(
self._connect, self.reactor.NOW)
@@ -140,61 +144,104 @@ class Printer:
self.is_shutdown = False
self.async_shutdown_msg = ""
self.run_result = None
self.fileconfig = None
self.mcus = []
self.stats_cb = []
self.state_cb = []
def get_start_args(self):
return self.start_args
def _stats(self, eventtime, force_output=False):
toolhead = self.objects.get('toolhead')
if toolhead is None:
return eventtime + 1.
is_active = toolhead.check_active(eventtime)
if not is_active and not force_output:
return eventtime + 1.
out = []
out.append(self.gcode.stats(eventtime))
out.append(toolhead.stats(eventtime))
for m in self.mcus:
out.append(m.stats(eventtime))
logging.info("Stats %.1f: %s", eventtime, ' '.join(out))
return eventtime + 1.
def get_reactor(self):
return self.reactor
def get_state_message(self):
return self.state_message
def add_object(self, name, obj):
if obj in self.objects:
raise self.config_error(
"Printer object '%s' already created" % (name,))
self.objects[name] = obj
def _load_config(self):
self.fileconfig = ConfigParser.RawConfigParser()
def lookup_object(self, name, default=ConfigWrapper.sentinel):
if name in self.objects:
return self.objects[name]
if default is ConfigWrapper.sentinel:
raise self.config_error("Unknown config object '%s'" % (name,))
return default
def lookup_module_objects(self, module_name):
prefix = module_name + ' '
objs = [self.objects[n] for n in self.objects if n.startswith(prefix)]
if module_name in self.objects:
return [self.objects[module_name]] + objs
return objs
def set_rollover_info(self, name, info):
if self.bglogger is not None:
self.bglogger.set_rollover_info(name, info)
def _stats(self, eventtime, force_output=False):
stats = [cb(eventtime) for cb in self.stats_cb]
if max([s[0] for s in stats] + [force_output]):
logging.info("Stats %.1f: %s", eventtime,
' '.join([s[1] for s in stats]))
return eventtime + 1.
def try_load_module(self, config, section):
if section in self.objects:
return
module_parts = section.split()
module_name = module_parts[0]
py_name = os.path.join(os.path.dirname(__file__),
'extras', module_name + '.py')
if not os.path.exists(py_name):
return
mod = importlib.import_module('extras.' + module_name)
init_func = 'load_config'
if len(module_parts) > 1:
init_func = 'load_config_prefix'
init_func = getattr(mod, init_func, None)
if init_func is not None:
self.objects[section] = init_func(config.getsection(section))
def _read_config(self):
fileconfig = ConfigParser.RawConfigParser()
config_file = self.start_args['config_file']
res = self.fileconfig.read(config_file)
res = fileconfig.read(config_file)
if not res:
raise self.config_error("Unable to open config file %s" % (
config_file,))
if self.bglogger is not None:
ConfigLogger(self.fileconfig, self.bglogger)
ConfigLogger(fileconfig, self.bglogger)
# Create printer components
config = ConfigWrapper(self, 'printer')
for m in [pins, mcu, chipmisc, toolhead, extruder, heater, fan]:
config = ConfigWrapper(self, fileconfig, 'printer')
for m in [pins, mcu]:
m.add_printer_objects(self, config)
for section in fileconfig.sections():
self.try_load_module(config, section)
for m in [toolhead, extruder, heater]:
m.add_printer_objects(self, config)
self.mcus = mcu.get_printer_mcus(self)
# Validate that there are no undefined parameters in the config file
valid_sections = { s: 1 for s, o in self.all_config_options }
for section in self.fileconfig.sections():
for section in fileconfig.sections():
section = section.lower()
if section not in valid_sections:
if section not in valid_sections and section not in self.objects:
raise self.config_error("Unknown config file section '%s'" % (
section,))
for option in self.fileconfig.options(section):
for option in fileconfig.options(section):
option = option.lower()
if (section, option) not in self.all_config_options:
raise self.config_error(
"Unknown option '%s' in section '%s'" % (
option, section))
# Determine which printer objects have stats/state callbacks
self.stats_cb = [o.stats for o in self.objects.values()
if hasattr(o, 'stats')]
self.state_cb = [o.printer_state for o in self.objects.values()
if hasattr(o, 'printer_state')]
def _connect(self, eventtime):
self.reactor.unregister_timer(self.connect_timer)
try:
self._load_config()
for m in self.mcus:
m.connect()
self.gcode.connect()
self._read_config()
for cb in self.state_cb:
if self.state_message is not message_startup:
return self.reactor.NEVER
cb('connect')
self.state_message = message_ready
for cb in self.state_cb:
if self.state_message is not message_ready:
return self.reactor.NEVER
cb('ready')
if self.start_args.get('debugoutput') is None:
self.reactor.update_timer(self.stats_timer, self.reactor.NOW)
except (self.config_error, pins.error) as e:
@@ -227,32 +274,24 @@ class Printer:
run_result = self.run_result
try:
if run_result == 'shutdown':
self.invoke_shutdown(self.async_shutdown_msg, True)
self.invoke_shutdown(self.async_shutdown_msg)
continue
self._stats(self.reactor.monotonic(), force_output=True)
for m in self.mcus:
if run_result == 'firmware_restart':
for m in self.lookup_module_objects('mcu'):
m.microcontroller_restart()
m.disconnect()
for cb in self.state_cb:
cb('disconnect')
except:
logging.exception("Unhandled exception during post run")
return run_result
def get_state_message(self):
return self.state_message
def invoke_shutdown(self, msg, is_mcu_shutdown=False):
def invoke_shutdown(self, msg):
if self.is_shutdown:
return
self.is_shutdown = True
if is_mcu_shutdown:
self.state_message = "%s%s" % (msg, message_shutdown)
else:
self.state_message = "%s%s" % (msg, message_restart)
for m in self.mcus:
m.do_shutdown()
self.gcode.do_shutdown()
toolhead = self.objects.get('toolhead')
if toolhead is not None:
toolhead.do_shutdown()
for cb in self.state_cb:
cb('shutdown')
def invoke_async_shutdown(self, msg):
self.async_shutdown_msg = msg
self.request_exit("shutdown")

40
klippy/mathutil.py Normal file
View File

@@ -0,0 +1,40 @@
# Simple math helper functions
#
# Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
# Helper code that implements coordinate descent
def coordinate_descent(adj_params, params, error_func):
# Define potential changes
params = dict(params)
dp = {param_name: 1. for param_name in adj_params}
# Calculate the error
best_err = error_func(params)
threshold = 0.00001
rounds = 0
while sum(dp.values()) > threshold and rounds < 10000:
rounds += 1
for param_name in adj_params:
orig = params[param_name]
params[param_name] = orig + dp[param_name]
err = error_func(params)
if err < best_err:
# There was some improvement
best_err = err
dp[param_name] *= 1.1
continue
params[param_name] = orig - dp[param_name]
err = error_func(params)
if err < best_err:
# There was some improvement
best_err = err
dp[param_name] *= 1.1
continue
params[param_name] = orig
dp[param_name] *= 0.9
logging.info("Coordinate descent best_err: %s rounds: %d", best_err, rounds)
return params

View File

@@ -1,6 +1,6 @@
# Interface to Klipper micro-controller code
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, zlib, logging, math
@@ -18,11 +18,10 @@ class MCU_stepper:
self._step_pin = pin_params['pin']
self._invert_step = pin_params['invert']
self._dir_pin = self._invert_dir = None
self._commanded_pos = 0
self._commanded_pos = self._mcu_position_offset = 0.
self._step_dist = self._inv_step_dist = 1.
self._mcu_position_offset = 0
self._min_stop_interval = 0.
self._reset_cmd = self._get_position_cmd = None
self._reset_cmd_id = self._get_position_cmd = None
self._ffi_lib = self._stepqueue = None
def get_mcu(self):
return self._mcu
@@ -45,62 +44,62 @@ class MCU_stepper:
self._oid, self._step_pin, self._dir_pin,
self._mcu.seconds_to_clock(min_stop_interval),
self._invert_step))
step_cmd = self._mcu.lookup_command(
self._mcu.add_config_cmd(
"reset_step_clock oid=%d clock=0" % (self._oid,), is_init=True)
step_cmd_id = self._mcu.lookup_command_id(
"queue_step oid=%c interval=%u count=%hu add=%hi")
dir_cmd = self._mcu.lookup_command(
dir_cmd_id = self._mcu.lookup_command_id(
"set_next_step_dir oid=%c dir=%c")
self._reset_cmd = self._mcu.lookup_command(
self._reset_cmd_id = self._mcu.lookup_command_id(
"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()
self._stepqueue = ffi_main.gc(self._ffi_lib.stepcompress_alloc(
self._mcu.seconds_to_clock(max_error), step_cmd.msgid, dir_cmd.msgid,
self._mcu.seconds_to_clock(max_error), step_cmd_id, dir_cmd_id,
self._invert_dir, self._oid),
self._ffi_lib.stepcompress_free)
self._mcu.register_stepqueue(self._stepqueue)
def get_oid(self):
return self._oid
def get_step_dist(self):
return self._step_dist
def set_position(self, pos):
if pos >= 0.:
steppos = int(pos * self._inv_step_dist + 0.5)
else:
steppos = int(pos * self._inv_step_dist - 0.5)
steppos = pos * self._inv_step_dist
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_pos + self._mcu_position_offset
mcu_pos = self._commanded_pos + self._mcu_position_offset
if mcu_pos >= 0.:
return int(mcu_pos + 0.5)
return int(mcu_pos - 0.5)
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):
def note_homing_end(self, did_trigger=False):
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):
cmd = self._get_position_cmd.encode(self._oid)
params = self._mcu.send_with_response(cmd, '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, print_time):
clock = self._mcu.print_time_to_clock(print_time)
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)
data = (self._reset_cmd_id, self._oid, 0)
ret = self._ffi_lib.stepcompress_queue_msg(
self._stepqueue, data, len(data))
if ret:
raise error("Internal error in stepcompress")
if not did_trigger or self._mcu.is_fileoutput():
return
params = self._get_position_cmd.send_with_response(
[self._oid], response='stepper_position', response_oid=self._oid)
pos = params['pos']
if self._invert_dir:
pos = -pos
self._commanded_pos = pos - self._mcu_position_offset
def step(self, print_time, sdir):
count = self._ffi_lib.stepcompress_push(
self._stepqueue, print_time, sdir)
@@ -129,7 +128,8 @@ class MCU_stepper:
self._commanded_pos += count
class MCU_endstop:
error = error
class TimeoutError(Exception):
pass
RETRY_QUERY = 1.000
def __init__(self, mcu, pin_params):
self._mcu = mcu
@@ -137,83 +137,88 @@ class MCU_endstop:
self._pin = pin_params['pin']
self._pullup = pin_params['pullup']
self._invert = pin_params['invert']
self._cmd_queue = mcu.alloc_command_queue()
self._oid = self._home_cmd = self._query_cmd = None
self._homing = False
self._min_query_time = self._next_query_time = self._home_timeout = 0.
self._min_query_time = self._next_query_time = 0.
self._last_state = {}
def get_mcu(self):
return self._mcu
def add_stepper(self, stepper):
if stepper.get_mcu() is not self._mcu:
raise pins.error("Endstop and stepper must be on the same mcu")
self._steppers.append(stepper)
def get_steppers(self):
return list(self._steppers)
def build_config(self):
self._oid = self._mcu.create_oid()
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._mcu.add_config_cmd(
"end_stop_home oid=%d clock=0 sample_ticks=0 sample_count=0"
" rest_ticks=0 pin_value=0" % (self._oid,), is_init=True)
for i, s in enumerate(self._steppers):
self._mcu.add_config_cmd(
"end_stop_set_stepper oid=%d pos=%d stepper_oid=%d" % (
self._oid, i, s.get_oid()), is_init=True)
cmd_queue = self._mcu.alloc_command_queue()
self._home_cmd = self._mcu.lookup_command(
"end_stop_home oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u pin_value=%c")
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c")
" rest_ticks=%u pin_value=%c", cq=cmd_queue)
self._query_cmd = self._mcu.lookup_command("end_stop_query oid=%c",
cq=cmd_queue)
self._mcu.register_msg(self._handle_end_stop_state, "end_stop_state"
, self._oid)
def home_prepare(self):
pass
def home_start(self, print_time, sample_time, sample_count, rest_time):
clock = self._mcu.print_time_to_clock(print_time)
rest_ticks = int(rest_time * self._mcu.get_adjusted_freq())
self._homing = True
self._min_query_time = self._mcu.monotonic()
self._next_query_time = print_time + self.RETRY_QUERY
msg = self._home_cmd.encode(
self._oid, clock, self._mcu.seconds_to_clock(sample_time),
sample_count, rest_ticks, 1 ^ self._invert)
self._mcu.send(msg, reqclock=clock, cq=self._cmd_queue)
self._next_query_time = self._min_query_time + self.RETRY_QUERY
self._home_cmd.send(
[self._oid, clock, self._mcu.seconds_to_clock(sample_time),
sample_count, rest_ticks, 1 ^ self._invert], reqclock=clock)
for s in self._steppers:
s.note_homing_start(clock)
def home_finalize(self, print_time):
for s in self._steppers:
s.note_homing_finalized()
self._home_timeout = print_time
def home_wait(self):
def home_wait(self, home_end_time):
eventtime = self._mcu.monotonic()
while self._check_busy(eventtime):
while self._check_busy(eventtime, home_end_time):
eventtime = self._mcu.pause(eventtime + 0.1)
def home_finalize(self):
pass
def _handle_end_stop_state(self, params):
logging.debug("end_stop_state %s", params)
self._last_state = params
def _check_busy(self, eventtime):
def _check_busy(self, eventtime, home_end_time=0.):
# Check if need to send an end_stop_query command
if self._mcu.is_fileoutput():
return False
print_time = self._mcu.estimated_print_time(eventtime)
last_sent_time = self._last_state.get('#sent_time', -1.)
if last_sent_time >= self._min_query_time:
if last_sent_time >= self._min_query_time or self._mcu.is_fileoutput():
if not self._homing:
return False
if not self._last_state.get('homing', 0):
for s in self._steppers:
s.note_homing_triggered()
s.note_homing_end(did_trigger=True)
self._homing = False
return False
if print_time > self._home_timeout:
last_sent_print_time = self._mcu.estimated_print_time(last_sent_time)
if last_sent_print_time > home_end_time:
# Timeout - disable endstop checking
msg = self._home_cmd.encode(self._oid, 0, 0, 0, 0, 0)
self._mcu.send(msg, reqclock=0, cq=self._cmd_queue)
raise error("Timeout during endstop homing")
for s in self._steppers:
s.note_homing_end()
self._homing = False
self._home_cmd.send([self._oid, 0, 0, 0, 0, 0])
raise self.TimeoutError("Timeout during endstop homing")
if self._mcu.is_shutdown():
raise error("MCU is shutdown")
if print_time >= self._next_query_time:
self._next_query_time = print_time + self.RETRY_QUERY
msg = self._query_cmd.encode(self._oid)
self._mcu.send(msg, cq=self._cmd_queue)
if eventtime >= self._next_query_time:
self._next_query_time = eventtime + self.RETRY_QUERY
self._query_cmd.send([self._oid])
return True
def query_endstop(self, print_time):
self._homing = False
self._next_query_time = print_time
self._min_query_time = self._mcu.monotonic()
self._min_query_time = self._next_query_time = self._mcu.monotonic()
def query_endstop_wait(self):
eventtime = self._mcu.monotonic()
while self._check_busy(eventtime):
@@ -224,132 +229,122 @@ class MCU_digital_out:
def __init__(self, mcu, pin_params):
self._mcu = mcu
self._oid = None
self._static_value = None
self._pin = pin_params['pin']
self._invert = self._shutdown_value = pin_params['invert']
self._invert = pin_params['invert']
self._start_value = self._shutdown_value = self._invert
self._is_static = False
self._max_duration = 2.
self._last_clock = 0
self._last_value = None
self._cmd_queue = mcu.alloc_command_queue()
self._set_cmd = None
def get_mcu(self):
return self._mcu
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
def setup_static(self):
self._static_value = not self._invert
def setup_shutdown_value(self, value):
self._shutdown_value = (not not value) ^ self._invert
def setup_start_value(self, start_value, shutdown_value, is_static=False):
if is_static and start_value != shutdown_value:
raise pins.error("Static pin can not have shutdown value")
self._start_value = (not not start_value) ^ self._invert
self._shutdown_value = (not not shutdown_value) ^ self._invert
self._is_static = is_static
def build_config(self):
if self._static_value is not None:
if self._is_static:
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
self._pin, self._static_value))
self._pin, self._start_value))
return
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_digital_out oid=%d pin=%s value=%d default_value=%d"
" max_duration=%d" % (
self._oid, self._pin, self._invert, self._shutdown_value,
self._oid, self._pin, self._start_value, self._shutdown_value,
self._mcu.seconds_to_clock(self._max_duration)))
cmd_queue = self._mcu.alloc_command_queue()
self._set_cmd = self._mcu.lookup_command(
"schedule_digital_out oid=%c clock=%u value=%c")
"schedule_digital_out oid=%c clock=%u value=%c", cq=cmd_queue)
def set_digital(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
msg = self._set_cmd.encode(
self._oid, clock, (not not value) ^ self._invert)
self._mcu.send(msg, minclock=self._last_clock, reqclock=clock
, cq=self._cmd_queue)
self._set_cmd.send([self._oid, clock, (not not value) ^ self._invert],
minclock=self._last_clock, reqclock=clock)
self._last_clock = clock
self._last_value = value
def get_last_setting(self):
return self._last_value
def set_pwm(self, print_time, value):
self.set_digital(print_time, value >= 0.5)
class MCU_pwm:
def __init__(self, mcu, pin_params):
self._mcu = mcu
self._hard_pwm = False
self._hardware_pwm = False
self._cycle_time = 0.100
self._max_duration = 2.
self._oid = None
self._static_value = None
self._pin = pin_params['pin']
self._invert = pin_params['invert']
self._shutdown_value = float(self._invert)
self._start_value = self._shutdown_value = float(self._invert)
self._is_static = False
self._last_clock = 0
self._pwm_max = 0.
self._cmd_queue = mcu.alloc_command_queue()
self._set_cmd = None
def get_mcu(self):
return self._mcu
def setup_max_duration(self, max_duration):
self._max_duration = max_duration
def setup_cycle_time(self, cycle_time):
def setup_cycle_time(self, cycle_time, hardware_pwm=False):
self._cycle_time = cycle_time
self._hard_pwm = False
def setup_hard_pwm(self, hard_cycle_ticks):
if not hard_cycle_ticks:
return
self._cycle_time = hard_cycle_ticks
self._hard_pwm = True
def setup_static_pwm(self, value):
self._hardware_pwm = hardware_pwm
def setup_start_value(self, start_value, shutdown_value, is_static=False):
if is_static and start_value != shutdown_value:
raise pins.error("Static pin can not have shutdown value")
if self._invert:
value = 1. - value
self._static_value = max(0., min(1., value))
def setup_shutdown_value(self, value):
if self._invert:
value = 1. - value
self._shutdown_value = max(0., min(1., value))
start_value = 1. - start_value
shutdown_value = 1. - shutdown_value
self._start_value = max(0., min(1., start_value))
self._shutdown_value = max(0., min(1., shutdown_value))
self._is_static = is_static
def build_config(self):
if self._hard_pwm:
cmd_queue = self._mcu.alloc_command_queue()
cycle_ticks = self._mcu.seconds_to_clock(self._cycle_time)
if self._hardware_pwm:
self._pwm_max = self._mcu.get_constant_float("PWM_MAX")
if self._static_value is not None:
value = int(self._static_value * self._pwm_max + 0.5)
if self._is_static:
self._mcu.add_config_cmd(
"set_pwm_out pin=%s cycle_ticks=%d value=%d" % (
self._pin, self._cycle_time, value))
self._pin, cycle_ticks,
self._start_value * self._pwm_max))
return
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
" default_value=%d max_duration=%d" % (
self._oid, self._pin, self._cycle_time,
self._invert * self._pwm_max,
self._oid, self._pin, cycle_ticks,
self._start_value * self._pwm_max,
self._shutdown_value * self._pwm_max,
self._mcu.seconds_to_clock(self._max_duration)))
self._set_cmd = self._mcu.lookup_command(
"schedule_pwm_out oid=%c clock=%u value=%hu")
"schedule_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
else:
if (self._start_value not in [0., 1.]
or self._shutdown_value not in [0., 1.]):
raise pins.error(
"start and shutdown values must be 0.0 or 1.0 on soft pwm")
self._pwm_max = self._mcu.get_constant_float("SOFT_PWM_MAX")
if self._static_value is not None:
if self._static_value not in [0., 1.]:
raise pins.error(
"static value must be 0.0 or 1.0 on soft pwm")
if self._is_static:
self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
self._pin, self._static_value >= 0.5))
self._pin, self._start_value >= 0.5))
return
if self._shutdown_value not in [0., 1.]:
raise pins.error(
"shutdown value must be 0.0 or 1.0 on soft pwm")
self._oid = self._mcu.create_oid()
self._mcu.add_config_cmd(
"config_soft_pwm_out oid=%d pin=%s cycle_ticks=%d value=%d"
" default_value=%d max_duration=%d" % (
self._oid, self._pin,
self._mcu.seconds_to_clock(self._cycle_time),
self._invert, self._shutdown_value >= 0.5,
self._oid, self._pin, cycle_ticks,
self._start_value >= 0.5, self._shutdown_value >= 0.5,
self._mcu.seconds_to_clock(self._max_duration)))
self._set_cmd = self._mcu.lookup_command(
"schedule_soft_pwm_out oid=%c clock=%u value=%hu")
"schedule_soft_pwm_out oid=%c clock=%u value=%hu", cq=cmd_queue)
def set_pwm(self, print_time, value):
clock = self._mcu.print_time_to_clock(print_time)
if self._invert:
value = 1. - value
value = int(max(0., min(1., 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)
self._set_cmd.send([self._oid, clock, value],
minclock=self._last_clock, reqclock=clock)
self._last_clock = clock
class MCU_adc:
@@ -362,7 +357,6 @@ class MCU_adc:
self._report_clock = 0
self._oid = self._callback = None
self._inv_max_adc = 0.
self._cmd_queue = mcu.alloc_command_queue()
def get_mcu(self):
return self._mcu
def setup_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
@@ -408,7 +402,8 @@ class MCU:
def __init__(self, printer, config, clocksync):
self._printer = printer
self._clocksync = clocksync
self._name = config.section
self._reactor = printer.get_reactor()
self._name = config.get_name()
if self._name.startswith('mcu '):
self._name = self._name[4:]
# Serial port
@@ -418,19 +413,18 @@ class MCU:
or self._serialport.startswith("/tmp/klipper_host_")):
baud = config.getint('baud', 250000, minval=2400)
self._serial = serialhdl.SerialReader(
printer.reactor, self._serialport, baud)
self._reactor, self._serialport, baud)
# Restarts
self._restart_method = 'command'
if baud:
rmethods = {m: m for m in ['arduino', 'command', 'rpi_usb']}
rmethods = {m: m for m in [None, 'arduino', 'command', 'rpi_usb']}
self._restart_method = config.getchoice(
'restart_method', rmethods, 'arduino')
'restart_method', rmethods, None)
self._reset_cmd = self._config_reset_cmd = None
self._emergency_stop_cmd = None
self._is_shutdown = False
self._is_shutdown = self._is_timeout = False
self._shutdown_msg = ""
if printer.bglogger is not None:
printer.bglogger.set_rollover_info(self._name, None)
printer.set_rollover_info(self._name, None)
# Config building
pins.get_printer_pins(printer).register_chip(self._name, self)
self._oid_count = 0
@@ -481,7 +475,7 @@ class MCU:
logging.info("Attempting automated MCU '%s' restart: %s",
self._name, reason)
self._printer.request_exit('firmware_restart')
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.000)
self._reactor.pause(self._reactor.monotonic() + 2.000)
raise error("Attempt MCU '%s' restart failed" % (self._name,))
def _connect_file(self, pace=False):
# In a debugging mode. Open debug output file and read data dictionary
@@ -521,26 +515,25 @@ class MCU:
self._oid_count,))
# Resolve pin names
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))
except:
raise pins.error("Unable to translate pin name: %s" % (cmd,))
self._config_cmds = updated_cmds
mcu_type = self._serial.msgparser.get_constant('MCU')
pin_resolver = pins.PinResolver(mcu_type)
if self._pin_map is not None:
pin_resolver.update_aliases(self._pin_map)
for i, cmd in enumerate(self._config_cmds):
self._config_cmds[i] = pin_resolver.update_command(cmd)
for i, cmd in enumerate(self._init_cmds):
self._init_cmds[i] = pin_resolver.update_command(cmd)
# 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,))
def _send_config(self):
msg = self.create_command("get_config")
get_config_cmd = self.lookup_command("get_config")
if self.is_fileoutput():
config_params = {
'is_config': 0, 'move_count': 500, 'crc': self._config_crc}
else:
config_params = self.send_with_response(msg, 'config')
config_params = get_config_cmd.send_with_response(response='config')
if not config_params['is_config']:
if self._restart_method == 'rpi_usb':
# Only configure mcu after usb power reset
@@ -549,9 +542,10 @@ class MCU:
logging.info("Sending MCU '%s' printer configuration...",
self._name)
for c in self._config_cmds:
self.send(self.create_command(c))
self._serial.send(c)
if not self.is_fileoutput():
config_params = self.send_with_response(msg, 'config')
config_params = get_config_cmd.send_with_response(
response='config')
if not config_params['is_config']:
if self._is_shutdown:
raise error("MCU '%s' error during config: %s" % (
@@ -566,23 +560,22 @@ class MCU:
raise error("MCU '%s' CRC does not match config" % (self._name,))
move_count = config_params['move_count']
logging.info("Configured MCU '%s' (%d moves)", self._name, move_count)
if self._printer.bglogger is not None:
msgparser = self._serial.msgparser
info = [
"Configured MCU '%s' (%d moves)" % (self._name, move_count),
"Loaded MCU '%s' %d commands (%s)" % (
"Loaded MCU '%s' %d commands (%s / %s)" % (
self._name, len(msgparser.messages_by_id),
msgparser.version),
msgparser.version, msgparser.build_versions),
"MCU '%s' config: %s" % (self._name, " ".join(
["%s=%s" % (k, v) for k, v in msgparser.config.items()]))]
self._printer.bglogger.set_rollover_info(self._name, "\n".join(info))
self._printer.set_rollover_info(self._name, "\n".join(info))
self._steppersync = self._ffi_lib.steppersync_alloc(
self._serial.serialqueue, self._stepqueues, len(self._stepqueues),
move_count)
self._ffi_lib.steppersync_set_time(self._steppersync, 0., self._mcu_freq)
for c in self._init_cmds:
self.send(self.create_command(c))
def connect(self):
self._serial.send(c)
def _connect(self):
if self.is_fileoutput():
self._connect_file()
else:
@@ -597,6 +590,12 @@ class MCU:
self._emergency_stop_cmd = self.lookup_command("emergency_stop")
self._reset_cmd = self.try_lookup_command("reset")
self._config_reset_cmd = self.try_lookup_command("config_reset")
if (self._restart_method is None
and (self._reset_cmd is not None
or self.config_reset_cmd is not None)
and self._serial.msgparser.get_constant(
'SERIAL_BAUD', None) is None):
self._restart_method = 'command'
self.register_msg(self.handle_shutdown, 'shutdown')
self.register_msg(self.handle_shutdown, 'is_shutdown')
self.register_msg(self.handle_mcu_stats, 'stats')
@@ -633,23 +632,19 @@ class MCU:
def get_max_stepper_error(self):
return self._max_stepper_error
# Wrapper functions
def send(self, cmd, minclock=0, reqclock=0, cq=None):
self._serial.send(cmd, minclock, reqclock, cq=cq)
def send_with_response(self, cmd, name, oid=None):
return self._serial.send_with_response(cmd, name, oid)
def register_msg(self, cb, msg, oid=None):
self._serial.register_callback(cb, msg, oid)
def alloc_command_queue(self):
return self._serial.alloc_command_queue()
def create_command(self, msg):
return self._serial.msgparser.create_command(msg)
def lookup_command(self, msgformat):
return self._serial.msgparser.lookup_command(msgformat)
def lookup_command(self, msgformat, cq=None):
return self._serial.lookup_command(msgformat, cq)
def try_lookup_command(self, msgformat):
try:
return self._serial.msgparser.lookup_command(msgformat)
return self.lookup_command(msgformat)
except self._serial.msgparser.error as e:
return None
def lookup_command_id(self, msgformat):
return self._serial.msgparser.lookup_command(msgformat).msgid
def get_constant_float(self, name):
return self._serial.msgparser.get_constant_float(name)
def print_time_to_clock(self, print_time):
@@ -663,38 +658,46 @@ class MCU:
def clock32_to_clock64(self, clock32):
return self._clocksync.clock32_to_clock64(clock32)
def pause(self, waketime):
return self._printer.reactor.pause(waketime)
return self._reactor.pause(waketime)
def monotonic(self):
return self._printer.reactor.monotonic()
return self._reactor.monotonic()
# Restarts
def _disconnect(self):
self._serial.disconnect()
if self._steppersync is not None:
self._ffi_lib.steppersync_free(self._steppersync)
self._steppersync = None
def _shutdown(self, force=False):
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
return
self._emergency_stop_cmd.send()
def _restart_arduino(self):
logging.info("Attempting MCU '%s' reset", self._name)
self.disconnect()
serialhdl.arduino_reset(self._serialport, self._printer.reactor)
self._disconnect()
serialhdl.arduino_reset(self._serialport, self._reactor)
def _restart_via_command(self):
reactor = self._printer.reactor
if ((self._reset_cmd is None and self._config_reset_cmd is None)
or not self._clocksync.is_active(reactor.monotonic())):
or not self._clocksync.is_active(self._reactor.monotonic())):
logging.info("Unable to issue reset command on MCU '%s'", self._name)
return
if self._reset_cmd is None:
# Attempt reset via config_reset command
logging.info("Attempting MCU '%s' config_reset command", self._name)
self._is_shutdown = True
self.do_shutdown(force=True)
reactor.pause(reactor.monotonic() + 0.015)
self.send(self._config_reset_cmd.encode())
self._shutdown(force=True)
self._reactor.pause(self._reactor.monotonic() + 0.015)
self._config_reset_cmd.send()
else:
# Attempt reset via reset command
logging.info("Attempting MCU '%s' reset command", self._name)
self.send(self._reset_cmd.encode())
reactor.pause(reactor.monotonic() + 0.015)
self.disconnect()
self._reset_cmd.send()
self._reactor.pause(self._reactor.monotonic() + 0.015)
self._disconnect()
def _restart_rpi_usb(self):
logging.info("Attempting MCU '%s' reset via rpi usb power", self._name)
self.disconnect()
self._disconnect()
chelper.run_hub_ctrl(0)
self._printer.reactor.pause(self._printer.reactor.monotonic() + 2.)
self._reactor.pause(self._reactor.monotonic() + 2.)
chelper.run_hub_ctrl(1)
def microcontroller_restart(self):
if self._restart_method == 'rpi_usb':
@@ -723,8 +726,10 @@ class MCU:
return
offset, freq = self._clocksync.calibrate_clock(print_time, eventtime)
self._ffi_lib.steppersync_set_time(self._steppersync, offset, freq)
if self._clocksync.is_active(eventtime) or self.is_fileoutput():
if (self._clocksync.is_active(eventtime) or self.is_fileoutput()
or self._is_timeout):
return
self._is_timeout = True
logging.info("Timeout with MCU '%s' (eventtime=%f)",
self._name, eventtime)
self._printer.invoke_shutdown("Lost communication with MCU '%s'" % (
@@ -733,19 +738,17 @@ class MCU:
msg = "%s: mcu_awake=%.03f mcu_task_avg=%.06f mcu_task_stddev=%.06f" % (
self._name, self._mcu_tick_awake, self._mcu_tick_avg,
self._mcu_tick_stddev)
return ' '.join([msg, self._serial.stats(eventtime),
return False, ' '.join([msg, self._serial.stats(eventtime),
self._clocksync.stats(eventtime)])
def do_shutdown(self, force=False):
if self._emergency_stop_cmd is None or (self._is_shutdown and not force):
return
self.send(self._emergency_stop_cmd.encode())
def disconnect(self):
self._serial.disconnect()
if self._steppersync is not None:
self._ffi_lib.steppersync_free(self._steppersync)
self._steppersync = None
def printer_state(self, state):
if state == 'connect':
self._connect()
elif state == 'disconnect':
self._disconnect()
elif state == 'shutdown':
self._shutdown()
def __del__(self):
self.disconnect()
self._disconnect()
Common_MCU_errors = {
("Timer too close", "No next step", "Missed scheduling of next "): """
@@ -771,20 +774,14 @@ def error_help(msg):
return ""
def add_printer_objects(printer, config):
mainsync = clocksync.ClockSync(printer.reactor)
reactor = printer.get_reactor()
mainsync = clocksync.ClockSync(reactor)
printer.add_object('mcu', MCU(printer, config.getsection('mcu'), mainsync))
for s in config.get_prefix_sections('mcu '):
printer.add_object(s.section, MCU(
printer, s, clocksync.SecondarySync(printer.reactor, mainsync)))
def get_printer_mcus(printer):
return [printer.objects[n] for n in sorted(printer.objects)
if n.startswith('mcu')]
printer, s, clocksync.SecondarySync(reactor, mainsync)))
def get_printer_mcu(printer, name):
mcu_name = name
if name != 'mcu':
mcu_name = 'mcu ' + name
if mcu_name not in printer.objects:
raise printer.config_error("Unknown MCU %s" % (name,))
return printer.objects[mcu_name]
if name == 'mcu':
return printer.lookup_object(name)
return printer.lookup_object('mcu ' + name)

View File

@@ -108,7 +108,7 @@ class MessageFormat:
self.param_types = [MessageTypes[fmt] for name, fmt in argparts]
self.param_names = [(name, MessageTypes[fmt]) for name, fmt in argparts]
self.name_to_type = dict(self.param_names)
def encode(self, *params):
def encode(self, params):
out = []
out.append(self.msgid)
for i, t in enumerate(self.param_types):
@@ -188,7 +188,7 @@ class MessageParser:
self.messages_by_name = {}
self.static_strings = {}
self.config = {}
self.version = ""
self.version = self.build_versions = ""
self.raw_identify_data = ""
self._init_messages(DefaultMessages, DefaultMessages.keys())
def check_packet(self, s):
@@ -318,17 +318,22 @@ class MessageParser:
self.static_strings = { int(k): v for k, v in static_strings.items() }
self.config.update(data.get('config', {}))
self.version = data.get('version', '')
self.build_versions = data.get('build_versions', '')
except error as e:
raise
except Exception as e:
logging.exception("process_identify error")
raise error("Error during identify: %s" % (str(e),))
def get_constant(self, name):
try:
return self.config[name]
except KeyError:
class sentinel: pass
def get_constant(self, name, default=sentinel):
if name not in self.config:
if default is not self.sentinel:
return default
raise error("Firmware constant '%s' not found" % (name,))
def get_constant_float(self, name):
return self.config[name]
def get_constant_float(self, name, default=sentinel):
if name not in self.config and default is not self.sentinel:
return default
try:
return float(self.config[name])
except ValueError:

View File

@@ -1,6 +1,6 @@
# Pin name to pin number definitions
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import re
@@ -31,9 +31,9 @@ def beaglebone_pins():
return gpios
MCU_PINS = {
"atmega168": port_pins(4), "atmega328": port_pins(4),
"atmega168": port_pins(5), "atmega328": port_pins(5),
"atmega644p": port_pins(4), "atmega1284p": port_pins(4),
"at90usb1286": port_pins(6),
"at90usb1286": port_pins(6), "at90usb646": port_pins(6),
"atmega1280": port_pins(12), "atmega2560": port_pins(12),
"sam3x8e": port_pins(4, 32),
"pru": beaglebone_pins(),
@@ -95,6 +95,7 @@ Arduino_Due_analog = [
Arduino_from_mcu = {
"atmega168": (Arduino_standard, Arduino_analog_standard),
"atmega328": (Arduino_standard, Arduino_analog_standard),
"atmega644p": (Sanguino, Sanguino_analog),
"atmega1280": (Arduino_mega, Arduino_analog_mega),
"atmega2560": (Arduino_mega, Arduino_analog_mega),
@@ -151,20 +152,31 @@ def update_map_beaglebone(pins, mcu):
# Command translation
######################################################################
# Obtains the pin mappings
def get_pin_map(mcu, mapping_name=None):
pins = dict(MCU_PINS.get(mcu, {}))
if mapping_name == 'arduino':
update_map_arduino(pins, mcu)
elif mapping_name == 'beaglebone':
update_map_beaglebone(pins, mcu)
return pins
# Translate pin names in a firmware command
re_pin = re.compile(r'(?P<prefix>[ _]pin=)(?P<name>[^ ]*)')
def update_command(cmd, pmap):
class PinResolver:
def __init__(self, mcu_type, validate_aliases=True):
self.mcu_type = mcu_type
self.validate_aliases = validate_aliases
self.pins = dict(MCU_PINS.get(mcu_type, {}))
self.active_pins = {}
def update_aliases(self, mapping_name):
self.pins = dict(MCU_PINS.get(self.mcu_type, {}))
if mapping_name == 'arduino':
update_map_arduino(self.pins, self.mcu_type)
elif mapping_name == 'beaglebone':
update_map_beaglebone(self.pins, self.mcu_type)
def update_command(self, cmd):
def pin_fixup(m):
return m.group('prefix') + str(pmap[m.group('name')])
name = m.group('name')
if name not in self.pins:
raise error("Unable to translate pin name: %s" % (cmd,))
pin_id = self.pins[name]
if (name != self.active_pins.setdefault(pin_id, name)
and self.validate_aliases):
raise error("pin %s is an alias for %s" % (
name, self.active_pins[pin_id]))
return m.group('prefix') + str(pin_id)
return re_pin.sub(pin_fixup, cmd)
@@ -179,22 +191,49 @@ class PrinterPins:
error = error
def __init__(self):
self.chips = {}
def parse_pin_desc(self, pin_desc, can_invert=False, can_pullup=False):
self.active_pins = {}
def lookup_pin(self, pin_type, pin_desc, share_type=None):
can_invert = pin_type in ['stepper', 'endstop', 'digital_out', 'pwm']
can_pullup = pin_type == 'endstop'
desc = pin_desc
pullup = invert = 0
if can_pullup and pin_desc.startswith('^'):
if can_pullup and desc.startswith('^'):
pullup = 1
pin_desc = pin_desc[1:].strip()
if can_invert and pin_desc.startswith('!'):
desc = desc[1:].strip()
if can_invert and desc.startswith('!'):
invert = 1
pin_desc = pin_desc[1:].strip()
if ':' not in pin_desc:
chip_name, pin = 'mcu', pin_desc
desc = desc[1:].strip()
if ':' not in desc:
chip_name, pin = 'mcu', desc
else:
chip_name, pin = [s.strip() for s in pin_desc.split(':', 1)]
chip_name, pin = [s.strip() for s in desc.split(':', 1)]
if chip_name not in self.chips:
raise error("Unknown pin chip name '%s'" % (chip_name,))
return {'chip': self.chips[chip_name], 'pin': pin,
'invert': invert, 'pullup': pullup}
if [c for c in '^!: ' if c in pin]:
format = ""
if can_pullup:
format += "[^] "
if can_invert:
format += "[!] "
raise error("Invalid pin description '%s'\n"
"Format is: %s[chip_name:] pin_name" % (
pin_desc, format))
share_name = "%s:%s" % (chip_name, pin)
if share_name in self.active_pins:
pin_params = self.active_pins[share_name]
if share_type is None or share_type != pin_params['share_type']:
raise error("pin %s used multiple times in config" % (pin,))
if invert != pin_params['invert'] or pullup != pin_params['pullup']:
raise error("Shared pin %s must have same polarity" % (pin,))
return pin_params
pin_params = {'chip': self.chips[chip_name], 'chip_name': chip_name,
'type': pin_type, 'share_type': share_type,
'pin': pin, 'invert': invert, 'pullup': pullup}
self.active_pins[share_name] = pin_params
return pin_params
def setup_pin(self, pin_type, pin_desc):
pin_params = self.lookup_pin(pin_type, pin_desc)
return pin_params['chip'].setup_pin(pin_params)
def register_chip(self, chip_name, chip):
chip_name = chip_name.strip()
if chip_name in self.chips:
@@ -205,12 +244,7 @@ def add_printer_objects(printer, config):
printer.add_object('pins', PrinterPins())
def get_printer_pins(printer):
return printer.objects['pins']
return printer.lookup_object('pins')
def setup_pin(printer, pin_type, pin_desc):
ppins = get_printer_pins(printer)
can_invert = pin_type in ['stepper', 'endstop', 'digital_out', 'pwm']
can_pullup = pin_type == 'endstop'
pin_params = ppins.parse_pin_desc(pin_desc, can_invert, can_pullup)
pin_params['type'] = pin_type
return pin_params['chip'].setup_pin(pin_params)
return get_printer_pins(printer).setup_pin(pin_type, pin_desc)

View File

@@ -84,13 +84,14 @@ class SerialReader:
msgparser.process_identify(identify_data)
self.msgparser = msgparser
self.register_callback(self.handle_unknown, '#unknown')
logging.info("Loaded %d commands (%s)",
len(msgparser.messages_by_id), msgparser.version)
logging.info("Loaded %d commands (%s / %s)",
len(msgparser.messages_by_id),
msgparser.version, msgparser.build_versions)
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:
mcu_baud = msgparser.get_constant_float('SERIAL_BAUD', None)
if mcu_baud is not None:
baud_adjust = self.BITS_PER_BYTE / mcu_baud
self.ffi_lib.serialqueue_set_baud_adjust(
self.serialqueue, baud_adjust)
@@ -125,17 +126,17 @@ class SerialReader:
with self.lock:
del self.handlers[name, oid]
# Command sending
def send(self, cmd, minclock=0, reqclock=0, cq=None):
def raw_send(self, cmd, minclock, reqclock, cmd_queue):
self.ffi_lib.serialqueue_send(
self.serialqueue, cmd_queue, cmd, len(cmd), minclock, reqclock)
def send(self, msg, minclock=0, reqclock=0):
cmd = self.msgparser.create_command(msg)
self.raw_send(cmd, minclock, reqclock, self.default_cmd_queue)
def lookup_command(self, msgformat, cq=None):
if cq is None:
cq = self.default_cmd_queue
self.ffi_lib.serialqueue_send(
self.serialqueue, cq, cmd, len(cmd), minclock, reqclock)
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, oid=None):
src = SerialRetryCommand(self, cmd, name, oid)
return src.get_response()
cmd = self.msgparser.lookup_command(msgformat)
return SerialCommand(self, cq, cmd)
def alloc_command_queue(self):
return self.ffi_main.gc(self.ffi_lib.serialqueue_alloc_commandqueue(),
self.ffi_lib.serialqueue_free_commandqueue)
@@ -174,6 +175,20 @@ class SerialReader:
def __del__(self):
self.disconnect()
# Wrapper around command sending
class SerialCommand:
def __init__(self, serial, cmd_queue, cmd):
self.serial = serial
self.cmd_queue = cmd_queue
self.cmd = cmd
def send(self, data=(), minclock=0, reqclock=0):
cmd = self.cmd.encode(data)
self.serial.raw_send(cmd, minclock, reqclock, self.cmd_queue)
def send_with_response(self, data=(), response=None, response_oid=None):
cmd = self.cmd.encode(data)
src = SerialRetryCommand(self.serial, cmd, response, response_oid)
return src.get_response()
# Class to retry sending of a query command until a given response is received
class SerialRetryCommand:
TIMEOUT_TIME = 5.0
@@ -194,7 +209,7 @@ class SerialRetryCommand:
def send_event(self, eventtime):
if self.response is not None:
return self.serial.reactor.NEVER
self.serial.send(self.cmd)
self.serial.raw_send(self.cmd, 0, 0, self.serial.default_cmd_queue)
return eventtime + self.RETRY_TIME
def handle_callback(self, params):
last_sent_time = params['#sent_time']
@@ -216,7 +231,7 @@ class SerialBootStrap:
def __init__(self, serial):
self.serial = serial
self.identify_data = ""
self.identify_cmd = self.serial.msgparser.lookup_command(
self.identify_cmd = self.serial.lookup_command(
"identify offset=%u count=%c")
self.is_done = False
self.serial.register_callback(self.handle_identify, 'identify_response')
@@ -240,13 +255,11 @@ class SerialBootStrap:
self.is_done = True
return
self.identify_data += msgdata
imsg = self.identify_cmd.encode(len(self.identify_data), 40)
self.serial.send(imsg)
self.identify_cmd.send([len(self.identify_data), 40])
def send_event(self, eventtime):
if self.is_done:
return self.serial.reactor.NEVER
imsg = self.identify_cmd.encode(len(self.identify_data), 40)
self.serial.send(imsg)
self.identify_cmd.send([len(self.identify_data), 40])
return eventtime + self.RETRY_TIME
def handle_unknown(self, params):
logging.debug("Unknown message %d (len %d) while identifying",

View File

@@ -147,7 +147,6 @@ pollreactor_check_timers(struct pollreactor *pr, double eventtime)
static void
pollreactor_run(struct pollreactor *pr)
{
pr->must_exit = 0;
double eventtime = get_monotonic();
while (! pr->must_exit) {
int timeout = pollreactor_check_timers(pr, eventtime);
@@ -390,6 +389,7 @@ struct serialqueue {
#define MIN_RTO 0.025
#define MAX_RTO 5.000
#define MIN_REQTIME_DELTA 0.250
#define MIN_BACKGROUND_DELTA 0.005
#define IDLE_QUERY_TIME 1.0
#define DEBUG_QUEUE_SENT 100
@@ -723,8 +723,13 @@ check_send_command(struct serialqueue *sq, double eventtime)
if (!list_empty(&cq->ready_queue)) {
struct queue_message *qm = list_first_entry(
&cq->ready_queue, struct queue_message, node);
if (qm->req_clock < min_ready_clock)
min_ready_clock = qm->req_clock;
uint64_t req_clock = qm->req_clock;
if (req_clock == BACKGROUND_PRIORITY_CLOCK)
req_clock = (uint64_t)(
(sq->idle_time - sq->last_clock_time + MIN_BACKGROUND_DELTA)
* sq->est_freq) + sq->last_clock;
if (req_clock < min_ready_clock)
min_ready_clock = req_clock;
}
}
@@ -908,7 +913,8 @@ serialqueue_send_batch(struct serialqueue *sq, struct command_queue *cq
int len = 0;
struct queue_message *qm;
list_for_each_entry(qm, msgs, node) {
if (qm->min_clock + (1LL<<31) < qm->req_clock)
if (qm->min_clock + (1LL<<31) < qm->req_clock
&& qm->req_clock != BACKGROUND_PRIORITY_CLOCK)
qm->min_clock = qm->req_clock - (1LL<<31);
len += qm->len;
}

View File

@@ -3,7 +3,8 @@
#include "list.h" // struct list_head
#define MAX_CLOCK 0x7fffffffffffffff
#define MAX_CLOCK 0x7fffffffffffffffLL
#define BACKGROUND_PRIORITY_CLOCK 0x7fffffff00000000LL
#define MESSAGE_MIN 5
#define MESSAGE_MAX 64

View File

@@ -423,11 +423,14 @@ queue_append_slow(struct stepcompress *sc, double rel_sc)
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));
uint32_t flush = *(sc->queue_next-65535) - (uint32_t)sc->last_step_clock;
int ret = stepcompress_flush(sc, sc->last_step_clock + flush);
if (ret)
return ret;
}
if (sc->queue_next >= sc->queue_end) {
// Make room in the queue
int in_use = sc->queue_next - sc->queue_pos;
if (sc->queue_pos > sc->queue) {
// Shuffle the internal queue to avoid having to allocate more ram
@@ -444,6 +447,8 @@ queue_append_slow(struct stepcompress *sc, double rel_sc)
}
sc->queue_pos = sc->queue;
sc->queue_next = sc->queue + in_use;
}
*sc->queue_next++ = abs_step_clock;
return 0;
}

View File

@@ -1,31 +1,57 @@
# Printer stepper support
#
# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
import homing, pins
class PrinterStepper:
def __init__(self, printer, config, name):
self.name = name
# Tracking of shared stepper enable pins
class StepperEnablePin:
def __init__(self, mcu_enable, enable_count=0):
self.mcu_enable = mcu_enable
self.enable_count = enable_count
def set_enable(self, print_time, enable):
if enable:
if not self.enable_count:
self.mcu_enable.set_digital(print_time, 1)
self.enable_count += 1
else:
self.enable_count -= 1
if not self.enable_count:
self.mcu_enable.set_digital(print_time, 0)
self.step_dist = config.getfloat('step_distance', above=0.)
self.inv_step_dist = 1. / self.step_dist
self.min_stop_interval = 0.
def lookup_enable_pin(printer, pin):
if pin is None:
return StepperEnablePin(None, 9999)
pin_params = pins.get_printer_pins(printer).lookup_pin(
'digital_out', pin, 'stepper_enable')
enable = pin_params.get('class')
if enable is None:
mcu_enable = pin_params['chip'].setup_pin(pin_params)
mcu_enable.setup_max_duration(0.)
pin_params['class'] = enable = StepperEnablePin(mcu_enable)
return enable
# Code storing the definitions for a stepper motor
class PrinterStepper:
def __init__(self, printer, config):
self.name = config.get_name()
if self.name.startswith('stepper_'):
self.name = self.name[8:]
self.need_motor_enable = True
# Stepper definition
self.mcu_stepper = pins.setup_pin(
printer, 'stepper', config.get('step_pin'))
dir_pin_params = pins.get_printer_pins(printer).parse_pin_desc(
config.get('dir_pin'), can_invert=True)
dir_pin_params = pins.get_printer_pins(printer).lookup_pin(
'digital_out', config.get('dir_pin'))
self.mcu_stepper.setup_dir_pin(dir_pin_params)
self.step_dist = config.getfloat('step_distance', above=0.)
self.mcu_stepper.setup_step_distance(self.step_dist)
enable_pin = config.get('enable_pin', None)
self.mcu_enable = None
if enable_pin is not None:
self.mcu_enable = pins.setup_pin(printer, 'digital_out', enable_pin)
self.mcu_enable.setup_max_duration(0.)
self.need_motor_enable = True
self.step = self.mcu_stepper.step
self.step_const = self.mcu_stepper.step_const
self.step_delta = self.mcu_stepper.step_delta
self.enable = lookup_enable_pin(printer, config.get('enable_pin', None))
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
@@ -38,27 +64,34 @@ class PrinterStepper:
2. * self.step_dist, max_halt_velocity, max_accel)
min_stop_interval = second_last_step_time - last_step_time
self.mcu_stepper.setup_min_stop_interval(min_stop_interval)
def set_position(self, pos):
self.mcu_stepper.set_position(pos)
def motor_enable(self, print_time, enable=0):
if enable and self.need_motor_enable:
self.mcu_stepper.reset_step_clock(print_time)
if (self.mcu_enable is not None
and self.mcu_enable.get_last_setting() != enable):
self.mcu_enable.set_digital(print_time, enable)
if self.need_motor_enable != (not enable):
self.enable.set_enable(print_time, enable)
self.need_motor_enable = not enable
# Support for stepper controlled linear axis with an endstop
class PrinterHomingStepper(PrinterStepper):
def __init__(self, printer, config, name):
PrinterStepper.__init__(self, printer, config, name)
def __init__(self, printer, config, default_position=None):
PrinterStepper.__init__(self, printer, config)
# Endstop and its position
self.mcu_endstop = pins.setup_pin(
printer, 'endstop', config.get('endstop_pin'))
self.mcu_endstop.add_stepper(self.mcu_stepper)
if default_position is None:
self.position_endstop = config.getfloat('position_endstop')
else:
self.position_endstop = config.getfloat(
'position_endstop', default_position)
# Axis range
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')
# Homing mechanics
self.homing_speed = config.getfloat('homing_speed', 5.0, above=0.)
self.homing_retract_dist = config.getfloat(
'homing_retract_dist', 5., minval=0.)
self.homing_positive_dir = config.getboolean('homing_positive_dir', None)
if self.homing_positive_dir is None:
axis_len = self.position_max - self.position_min
@@ -69,9 +102,8 @@ class PrinterHomingStepper(PrinterStepper):
else:
raise config.error(
"Unable to infer homing_positive_dir in section '%s'" % (
config.section,))
self.homing_retract_dist = config.getfloat(
'homing_retract_dist', 5., above=0.)
config.get_name(),))
# Endstop stepper phase position tracking
self.homing_stepper_phases = config.getint(
'homing_stepper_phases', None, minval=0)
endstop_accuracy = config.getfloat(
@@ -81,7 +113,8 @@ class PrinterHomingStepper(PrinterStepper):
self.homing_endstop_phase = config.getint(
'homing_endstop_phase', None, minval=0
, maxval=self.homing_stepper_phases-1)
if self.homing_endstop_phase is not None:
if (self.homing_endstop_phase is not None
and config.getboolean('homing_endstop_align_zero', False)):
# Adjust the endstop position so 0.0 is always at a full step
micro_steps = self.homing_stepper_phases // 4
phase_offset = (
@@ -99,32 +132,27 @@ class PrinterHomingStepper(PrinterStepper):
self.homing_endstop_accuracy = self.homing_stepper_phases//2 - 1
elif self.homing_endstop_phase is not None:
self.homing_endstop_accuracy = int(math.ceil(
endstop_accuracy * self.inv_step_dist / 2.))
endstop_accuracy * .5 / self.step_dist))
else:
self.homing_endstop_accuracy = int(math.ceil(
endstop_accuracy * self.inv_step_dist))
endstop_accuracy / self.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", name)
self.homing_stepper_phases = None
if self.mcu_endstop.get_mcu().is_fileoutput():
self.homing_endstop_accuracy = self.homing_stepper_phases
def get_homing_speed(self):
# Round the configured homing speed so that it is an even
# number of ticks per step.
adjusted_freq = self.mcu_stepper.get_mcu().get_adjusted_freq()
dist_ticks = adjusted_freq * self.step_dist
ticks_per_step = round(dist_ticks / self.homing_speed)
return dist_ticks / ticks_per_step
def get_endstops(self):
return [(self.mcu_endstop, self.name)]
def get_homed_offset(self):
if not self.homing_stepper_phases or self.need_motor_enable:
return 0
return 0.
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.name, pos)
self.homing_endstop_phase = pos
return 0
return 0.
delta = (pos - self.homing_endstop_phase) % self.homing_stepper_phases
if delta >= self.homing_stepper_phases - self.homing_endstop_accuracy:
delta -= self.homing_stepper_phases
@@ -133,3 +161,48 @@ class PrinterHomingStepper(PrinterStepper):
"Endstop %s incorrect phase (got %d vs %d)" % (
self.name, pos, self.homing_endstop_phase))
return delta * self.step_dist
# Wrapper for dual stepper motor support
class PrinterMultiStepper(PrinterHomingStepper):
def __init__(self, printer, config):
PrinterHomingStepper.__init__(self, printer, config)
self.endstops = PrinterHomingStepper.get_endstops(self)
self.extras = []
self.all_step_const = [self.step_const]
for i in range(1, 99):
if not config.has_section(config.get_name() + str(i)):
break
extraconfig = config.getsection(config.get_name() + str(i))
extra = PrinterStepper(printer, extraconfig)
self.extras.append(extra)
self.all_step_const.append(extra.step_const)
extraendstop = extraconfig.get('endstop_pin', None)
if extraendstop is not None:
mcu_endstop = pins.setup_pin(printer, 'endstop', extraendstop)
mcu_endstop.add_stepper(extra.mcu_stepper)
self.endstops.append((mcu_endstop, extra.name))
else:
self.mcu_endstop.add_stepper(extra.mcu_stepper)
self.step_const = self.step_multi_const
def step_multi_const(self, print_time, start_pos, dist, start_v, accel):
for step_const in self.all_step_const:
step_const(print_time, start_pos, dist, start_v, accel)
def set_max_jerk(self, max_halt_velocity, max_accel):
PrinterHomingStepper.set_max_jerk(self, max_halt_velocity, max_accel)
for extra in self.extras:
extra.set_max_jerk(max_halt_velocity, max_accel)
def set_position(self, pos):
PrinterHomingStepper.set_position(self, pos)
for extra in self.extras:
extra.set_position(pos)
def motor_enable(self, print_time, enable=0):
PrinterHomingStepper.motor_enable(self, print_time, enable)
for extra in self.extras:
extra.motor_enable(print_time, enable)
def get_endstops(self):
return self.endstops
def LookupMultiHomingStepper(printer, config):
if not config.has_section(config.get_name() + '1'):
return PrinterHomingStepper(printer, config)
return PrinterMultiStepper(printer, config)

View File

@@ -1,6 +1,6 @@
# Code for coordinating events on the printer toolhead
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
@@ -183,8 +183,8 @@ STALL_TIME = 0.100
class ToolHead:
def __init__(self, printer, config):
self.printer = printer
self.reactor = printer.reactor
self.all_mcus = mcu.get_printer_mcus(printer)
self.reactor = printer.get_reactor()
self.all_mcus = printer.lookup_module_objects('mcu')
self.mcu = self.all_mcus[0]
self.max_velocity = config.getfloat('max_velocity', above=0.)
self.max_accel = config.getfloat('max_accel', above=0.)
@@ -192,7 +192,7 @@ class ToolHead:
'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.)
'junction_deviation', 0.02, minval=0.)
self.move_queue = MoveQueue()
self.commanded_pos = [0., 0., 0., 0.]
# Print time tracking
@@ -205,10 +205,11 @@ class ToolHead:
self.move_flush_time = config.getfloat(
'move_flush_time', 0.050, above=0.)
self.print_time = 0.
self.last_print_start_time = 0.
self.need_check_stall = -1.
self.print_stall = 0
self.sync_print_time = True
self.last_flush_from_idle = False
self.idle_flush_print_time = 0.
self.flush_timer = self.reactor.register_timer(self._flush_handler)
self.move_queue.set_flush_time(self.buffer_time_high)
# Motor off tracking
@@ -234,19 +235,17 @@ class ToolHead:
if not self.sync_print_time:
return self.print_time
self.sync_print_time = False
est_print_time = self.mcu.estimated_print_time(self.reactor.monotonic())
if self.last_flush_from_idle and self.print_time > est_print_time:
self.print_stall += 1
self.last_flush_from_idle = False
self.need_motor_off = True
self.print_time = max(
self.print_time, est_print_time + self.buffer_time_start)
est_print_time = self.mcu.estimated_print_time(self.reactor.monotonic())
if est_print_time + self.buffer_time_start > self.print_time:
self.print_time = est_print_time + self.buffer_time_start
self.last_print_start_time = self.print_time
self.reactor.update_timer(self.flush_timer, self.reactor.NOW)
return self.print_time
def _flush_lookahead(self, must_sync=False):
sync_print_time = self.sync_print_time
self.move_queue.flush()
self.last_flush_from_idle = False
self.idle_flush_print_time = 0.
if sync_print_time or must_sync:
self.sync_print_time = True
self.move_queue.set_flush_time(self.buffer_time_high)
@@ -265,6 +264,11 @@ class ToolHead:
eventtime = self.reactor.monotonic()
if self.sync_print_time:
# Building initial queue - make sure to flush on idle input
if self.idle_flush_print_time:
est_print_time = self.mcu.estimated_print_time(eventtime)
if est_print_time < self.idle_flush_print_time:
self.print_stall += 1
self.idle_flush_print_time = 0.
self.reactor.update_timer(self.flush_timer, eventtime + 0.100)
return
# Check if there are lots of queued moves and stall if so
@@ -289,7 +293,7 @@ class ToolHead:
# Under ran low buffer mark - flush lookahead queue
self._flush_lookahead(must_sync=True)
if print_time != self.print_time:
self.last_flush_from_idle = True
self.idle_flush_print_time = self.print_time
except:
logging.exception("Exception in flush_handler")
self.printer.invoke_shutdown("Exception in flush_handler")
@@ -310,10 +314,10 @@ class ToolHead:
# Movement commands
def get_position(self):
return list(self.commanded_pos)
def set_position(self, newpos):
def set_position(self, newpos, homing_axes=()):
self._flush_lookahead()
self.commanded_pos[:] = newpos
self.kin.set_position(newpos)
self.kin.set_position(newpos, homing_axes)
def move(self, newpos, speed):
speed = min(speed, self.max_velocity)
move = Move(self, self.commanded_pos, newpos, speed)
@@ -327,11 +331,10 @@ class ToolHead:
self.move_queue.add_move(move)
if self.print_time > self.need_check_stall:
self._check_stall()
def home(self, homing_state):
self.kin.home(homing_state)
def dwell(self, delay):
def dwell(self, delay, check_stall=True):
self.get_last_move_time()
self.update_move_time(delay)
if check_stall:
self._check_stall()
def motor_off(self):
self.dwell(STALL_TIME)
@@ -349,9 +352,6 @@ class ToolHead:
while (not self.sync_print_time
or self.print_time >= self.mcu.estimated_print_time(eventtime)):
eventtime = self.reactor.pause(eventtime + 0.100)
def query_endstops(self, query_flags=""):
last_move_time = self.get_last_move_time()
return self.kin.query_endstops(last_move_time, query_flags)
def set_extruder(self, extruder):
last_move_time = self.get_last_move_time()
self.extruder.set_active(last_move_time, False)
@@ -360,23 +360,32 @@ class ToolHead:
self.move_queue.set_extruder(extruder)
self.commanded_pos[3] = extrude_pos
# Misc commands
def check_active(self, eventtime):
def stats(self, eventtime):
for m in self.all_mcus:
m.check_active(self.print_time, eventtime)
if not self.sync_print_time:
return True
return self.print_time + 60. > self.mcu.estimated_print_time(eventtime)
def stats(self, eventtime):
est_print_time = self.mcu.estimated_print_time(eventtime)
buffer_time = max(0., self.print_time - est_print_time)
return "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
self.print_time, buffer_time, self.print_stall)
def do_shutdown(self):
buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime)
is_active = buffer_time > -60. or not self.sync_print_time
return is_active, "print_time=%.3f buffer_time=%.3f print_stall=%d" % (
self.print_time, max(buffer_time, 0.), self.print_stall)
def get_status(self, eventtime):
buffer_time = self.print_time - self.mcu.estimated_print_time(eventtime)
if buffer_time > -1. or not self.sync_print_time:
status = "Printing"
elif self.need_motor_off:
status = "Ready"
else:
status = "Idle"
printing_time = self.print_time - self.last_print_start_time
return {'status': status, 'printing_time': printing_time}
def printer_state(self, state):
if state == 'shutdown':
try:
self.move_queue.reset()
self.reset_print_time()
except:
logging.exception("Exception in do_shutdown")
logging.exception("Exception in toolhead shutdown")
def get_kinematics(self):
return self.kin
def get_max_velocity(self):
return self.max_velocity, self.max_accel
def get_max_axis_halt(self):

View File

@@ -30,8 +30,7 @@ def create_pty(ptyname):
except os.error:
pass
os.symlink(os.ttyname(sfd), ptyname)
fcntl.fcntl(mfd, fcntl.F_SETFL
, fcntl.fcntl(mfd, fcntl.F_GETFL) | os.O_NONBLOCK)
set_nonblock(mfd)
old = termios.tcgetattr(mfd)
old[3] = old[3] & ~termios.ECHO
termios.tcsetattr(mfd, termios.TCSADRAIN, old)
@@ -58,7 +57,7 @@ def get_git_version():
if not os.path.exists(gitdir):
logging.debug("No '.git' file/directory found")
return "?"
prog = "git -C %s describe --tags --long --dirty" % (gitdir,)
prog = "git -C %s describe --always --tags --long --dirty" % (gitdir,)
try:
process = subprocess.Popen(shlex.split(prog), stdout=subprocess.PIPE)
output = process.communicate()[0]

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env python2
# Script to interact with simulavr by simulating a serial port.
#
# Copyright (C) 2015 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2015-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, time, os, pty, fcntl, termios, errno
import pysimulavr
@@ -107,23 +106,22 @@ class Pacing(pysimulavr.PySimulationMember):
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.next_check_clock = 0
self.rel_time = time.time()
self.best_offset = 0.
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
offset = clock * self.pacing_rate - (curtime - self.rel_time)
self.best_offset = max(self.best_offset, offset)
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
time.sleep(offset - 0.000040)
if clock >= self.next_check_clock:
self.rel_time -= min(self.best_offset, 0.)
self.next_check_clock = clock + self.delay * 500
self.best_offset = -999999999.
return self.delay
# Forward data from a terminal device to the serial port pins

View File

@@ -4,8 +4,7 @@
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, os, subprocess, optparse, logging, shlex, socket, time
import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback
import json, zlib
sys.path.append('./klippy')
import msgproto
@@ -182,7 +181,7 @@ const uint8_t command_index_size PROGMEM = ARRAY_SIZE(command_index);
######################################################################
def build_identify(cmd_by_id, msg_to_id, responses, static_strings
, constants, version):
, constants, version, toolstr):
#commands, messages, static_strings
messages = dict((msgid, msg) for msg, msgid in msg_to_id.items())
data = {}
@@ -193,6 +192,7 @@ def build_identify(cmd_by_id, msg_to_id, responses, static_strings
for i in range(len(static_strings)) }
data['config'] = constants
data['version'] = version
data['build_versions'] = toolstr
# Format compressed info into C code
data = json.dumps(data)
@@ -203,6 +203,9 @@ def build_identify(cmd_by_id, msg_to_id, responses, static_strings
out.append('\n ')
out.append(" 0x%02x," % (ord(zdata[i]),))
fmt = """
// version: %s
// build_versions: %s
const uint8_t command_identify_data[] PROGMEM = {%s
};
@@ -210,7 +213,7 @@ const uint8_t command_identify_data[] PROGMEM = {%s
const uint32_t command_identify_size PROGMEM
= ARRAY_SIZE(command_identify_data);
"""
return data, fmt % (''.join(out), len(zdata), len(data))
return data, fmt % (version, toolstr, ''.join(out), len(zdata), len(data))
######################################################################
@@ -241,7 +244,7 @@ def git_version():
if not os.path.exists('.git'):
logging.debug("No '.git' file/directory found")
return ""
ver = check_output("git describe --tags --long --dirty").strip()
ver = check_output("git describe --always --tags --long --dirty").strip()
logging.debug("Got git version: %s" % (repr(ver),))
return ver
@@ -254,6 +257,36 @@ def build_version(extra):
version = "%s-%s-%s%s" % (version, btime, hostname, extra)
return version
# Run "tool --version" for each specified tool and extract versions
def tool_versions(tools):
tools = [t.strip() for t in tools.split(';')]
versions = ['', '']
success = 0
for tool in tools:
# Extract first line from "tool --version" output
verstr = check_output("%s --version" % (tool,)).split('\n')[0]
# Check if this tool looks like a binutils program
isbinutils = 0
if verstr.startswith('GNU '):
isbinutils = 1
verstr = verstr[4:]
# Extract version information and exclude program name
if ' ' not in verstr:
continue
prog, ver = verstr.split(' ', 1)
if not prog or not ver:
continue
# Check for any version conflicts
if versions[isbinutils] and versions[isbinutils] != ver:
logging.debug("Mixed version %s vs %s" % (
repr(versions[isbinutils]), repr(ver)))
versions[isbinutils] = "mixed"
continue
versions[isbinutils] = ver
success += 1
cleanbuild = versions[0] and versions[1] and success == len(tools)
return cleanbuild, "gcc: %s binutils: %s" % (versions[0], versions[1])
######################################################################
# Main code
@@ -266,6 +299,8 @@ def main():
help="extra version string to append to version")
opts.add_option("-d", dest="write_dictionary",
help="file to write mcu protocol dictionary")
opts.add_option("-t", "--tools", dest="tools", default="",
help="list of build programs to extract version from")
opts.add_option("-v", action="store_true", dest="verbose",
help="enable debug messages")
@@ -349,12 +384,14 @@ def main():
cmdcode = build_commands(cmd_by_id, messages_by_name, all_param_types)
paramcode = build_param_types(all_param_types)
# Create identify information
cleanbuild, toolstr = tool_versions(options.tools)
version = build_version(options.extra)
sys.stdout.write("Version: %s\n" % (version,))
responses = [msg_to_id[msg] for msgname, msg in messages_by_name.items()
if msgname not in commands]
datadict, icode = build_identify(cmd_by_id, msg_to_id, responses
, static_strings, constants, version)
datadict, icode = build_identify(
cmd_by_id, msg_to_id, responses,
static_strings, constants, version, toolstr)
# Write output
f = open(outcfile, 'wb')
f.write(FILEHEADER + call_lists_code + static_strings_code

View File

@@ -1,18 +1,28 @@
#!/usr/bin/env python2
# Script to parse a logging file, extract the stats, and graph them
#
# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
# Copyright (C) 2016-2018 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import optparse, datetime
import matplotlib.pyplot as plt, matplotlib.dates as mdates
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
import matplotlib.ticker
MAXBANDWIDTH=25000.
MAXBUFFER=2.
STATS_INTERVAL=5.
TASK_MAX=0.0025
def parse_log(logname):
APPLY_PREFIX = ['mcu_awake', 'mcu_task_avg', 'mcu_task_stddev', 'bytes_write',
'bytes_read', 'bytes_retransmit', 'freq', 'adj']
def parse_log(logname, mcu):
if mcu is None:
mcu = "mcu"
mcu_prefix = mcu + ":"
apply_prefix = { p: 1 for p in APPLY_PREFIX }
f = open(logname, 'rb')
out = []
for line in f:
@@ -21,7 +31,18 @@ def parse_log(logname):
#if parts and parts[0] == 'INFO:root:shutdown:':
# break
continue
keyparts = dict(p.split('=', 1) for p in parts[2:])
prefix = ""
keyparts = {}
for p in parts[2:]:
if p.endswith(':'):
prefix = p
if prefix == mcu_prefix:
prefix = ''
continue
name, val = p.split('=', 1)
if name in apply_prefix:
name = prefix + name
keyparts[name] = val
if keyparts.get('bytes_write', '0') == '0':
continue
keyparts['#sampletime'] = float(parts[1][:-1])
@@ -30,27 +51,34 @@ def parse_log(logname):
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.
runoff_samples = {}
last_runoff_start = last_buffer_time = last_sampletime = 0.
last_print_stall = 0
for d in reversed(data):
# Check for buffer runoff
sampletime = d['#sampletime']
buffer_time = float(d.get('buffer_time', 0.))
if (last_runoff_start and last_sampletime - sampletime < 5
and buffer_time > last_buffer_time):
runoff_samples[last_runoff_start][1].append(sampletime)
elif buffer_time < 1.:
last_runoff_start = sampletime
runoff_samples[last_runoff_start] = [False, [sampletime]]
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
last_runoff_start = 0.
last_buffer_time = buffer_time
last_sampletime = sampletime
# Check for print stall
print_stall = int(d['print_stall'])
if print_stall < last_print_stall:
if last_runoff_start:
runoff_samples[last_runoff_start][0] = True
last_print_stall = print_stall
sample_resets = {sampletime: 1 for stall, samples in runoff_samples.values()
for sampletime in samples if not stall}
return sample_resets
def plot_mcu(data, maxbw, outname, graph_awake=False):
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'])
@@ -74,7 +102,7 @@ def plot_mcu(data, maxbw, outname, graph_awake=False):
load = 0.
pt = float(d['print_time'])
hb = float(d['buffer_time'])
if pt <= 2. * MAXBUFFER or hb >= MAXBUFFER or st in sample_resets:
if hb >= MAXBUFFER or st in sample_resets:
hb = 0.
else:
hb = 100. * (MAXBUFFER - hb) / MAXBUFFER
@@ -87,34 +115,77 @@ def plot_mcu(data, maxbw, outname, graph_awake=False):
lastbw = bw
# Build plot
fig, ax1 = plt.subplots()
fig, ax1 = matplotlib.pyplot.subplots()
ax1.set_title("MCU bandwidth and load utilization")
ax1.set_xlabel('Time')
ax1.set_ylabel('Usage (%)')
if graph_awake:
ax1.plot_date(times, awake, 'b', label='Awake time')
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(loc='best')
ax1.xaxis.set_major_formatter(mdates.DateFormatter('%H:%M'))
#plt.gcf().autofmt_xdate()
ax1.plot_date(times, bwdeltas, 'g', label='Bandwidth', alpha=0.8)
ax1.plot_date(times, loads, 'r', label='MCU load', alpha=0.8)
ax1.plot_date(times, hostbuffers, 'c', label='Host buffer', alpha=0.8)
ax1.plot_date(times, awake, 'y', label='Awake time', alpha=0.6)
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('x-small')
ax1.legend(loc='best', prop=fontP)
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
ax1.grid(True)
plt.savefig(outname)
fig.set_size_inches(8, 6)
fig.savefig(outname)
def plot_frequency(data, outname, mcu):
all_keys = {}
for d in data:
all_keys.update(d)
one_mcu = mcu is not None
graph_keys = { key: ([], []) for key in all_keys
if (key in ("freq", "adj") or (not one_mcu and (
key.endswith(":freq") or key.endswith(":adj")))) }
basetime = lasttime = data[0]['#sampletime']
for d in data:
st = datetime.datetime.utcfromtimestamp(d['#sampletime'])
for key, (times, values) in graph_keys.items():
val = d.get(key)
if val not in (None, '0', '1'):
times.append(st)
values.append(float(val))
# Build plot
fig, ax1 = matplotlib.pyplot.subplots()
if one_mcu:
ax1.set_title("MCU '%s' frequency" % (mcu,))
else:
ax1.set_title("MCU frequency")
ax1.set_xlabel('Time')
ax1.set_ylabel('Frequency')
for key in sorted(graph_keys):
times, values = graph_keys[key]
ax1.plot_date(times, values, '.', label=key)
fontP = matplotlib.font_manager.FontProperties()
fontP.set_size('x-small')
ax1.legend(loc='best', prop=fontP)
ax1.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%H:%M'))
ax1.yaxis.set_major_formatter(matplotlib.ticker.FormatStrFormatter('%d'))
ax1.grid(True)
fig.set_size_inches(8, 6)
fig.savefig(outname)
def main():
usage = "%prog [options] <logfile> <outname>"
opts = optparse.OptionParser(usage)
opts.add_option("-a", "--awake", action="store_true"
, help="graph mcu awake time")
opts.add_option("-f", "--frequency", action="store_true",
help="graph mcu frequency")
opts.add_option("-m", "--mcu", type="string", dest="mcu", default=None,
help="limit stats to the given mcu")
options, args = opts.parse_args()
if len(args) != 2:
opts.error("Incorrect number of arguments")
logname, outname = args
data = parse_log(logname)
data = parse_log(logname, options.mcu)
if not data:
return
plot_mcu(data, MAXBANDWIDTH, outname, graph_awake=options.awake)
if options.frequency:
plot_frequency(data, outname, options.mcu)
return
plot_mcu(data, MAXBANDWIDTH, outname)
if __name__ == '__main__':
main()

294
scripts/logextract.py Executable file
View File

@@ -0,0 +1,294 @@
#!/usr/bin/env python2
# Script to extract config and shutdown information file a klippy.log file
#
# Copyright (C) 2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, re, collections
def format_comment(line_num, line):
return "# %6d: %s" % (line_num, line)
######################################################################
# Config file extraction
######################################################################
class GatherConfig:
def __init__(self, configs, line_num, recent_lines, logname):
self.configs = configs
self.line_num = line_num
self.config_num = len(configs) + 1
self.filename = "%s.config%04d.cfg" % (logname, self.config_num)
self.config_lines = []
self.comments = []
def add_line(self, line_num, line):
if line != '=======================':
self.config_lines.append(line)
return True
self.finalize()
return False
def finalize(self):
lines = tuple(self.config_lines)
ch = self.configs.get(lines)
if ch is None:
self.configs[lines] = ch = self
else:
ch.comments.extend(self.comments)
ch.comments.append(format_comment(self.line_num, "config file"))
def add_comment(self, comment):
if comment is not None:
self.comments.append(comment)
def write_file(self):
f = open(self.filename, 'wb')
f.write('\n'.join(self.comments + self.config_lines))
f.close()
######################################################################
# Shutdown extraction
######################################################################
stats_seq_s = r" send_seq=(?P<sseq>[0-9]+) receive_seq=(?P<rseq>[0-9]+) "
count_s = r"(?P<count>[0-9]+)"
time_s = r"(?P<time>[0-9]+[.][0-9]+)"
esttime_s = r"(?P<esttime>[0-9]+[.][0-9]+)"
shortseq_s = r"(?P<shortseq>[0-9a-f])"
stats_r = re.compile(r"^Stats " + time_s + ": ")
serial_dump_r = re.compile(r"^Dumping serial stats: .*" + stats_seq_s)
send_dump_r = re.compile(r"^Dumping send queue " + count_s + " messages$")
sent_r = re.compile(r"^Sent " + count_s + " " + esttime_s + " " + time_s
+ " [0-9]+: seq: 1" + shortseq_s + ",")
receive_dump_r = re.compile(r"^Dumping receive queue " + count_s + " messages$")
receive_r = re.compile(r"^Receive: " + count_s + " " + time_s + " " + esttime_s
+ " [0-9]+: seq: 1" + shortseq_s + ",")
gcode_r = re.compile(r"^Read " + time_s + ": ['\"]")
clock_r = re.compile(r"^clocksync state: .* clock_est=\((?P<st>[^ ]+)"
+ r" (?P<sc>[0-9]+) (?P<f>[^ ]+)\)")
repl_seq_r = re.compile(r": seq: 1" + shortseq_s)
repl_clock_r = re.compile(r"clock=(?P<clock>[0-9]+) ")
mcu_r = re.compile(r"MCU '(?P<mcu>[^']+)' (is_)?shutdown: (?P<reason>.*)$")
def add_high_bits(val, ref, mask):
half = (mask + 1) // 2
return ref + ((val - (ref & mask) + half) & mask) - half
class GatherShutdown:
class mcu_info:
def __init__(self):
self.sent_stream = []
self.receive_stream = []
self.sent_stream = []
self.receive_stream = []
self.sent_time_to_seq = {}
self.sent_seq_to_time = {}
self.receive_seq_to_time = {}
self.clock_est = 0., 0., 1.
self.shutdown_seq = None
self.send_count = self.receive_count = 0
def __init__(self, configs, line_num, recent_lines, logname):
self.shutdown_line_num = line_num
self.filename = "%s.shutdown%05d" % (logname, line_num)
self.comments = []
if configs:
configs_by_id = {c.config_num: c for c in configs.values()}
config = configs_by_id[max(configs_by_id.keys())]
config.add_comment(format_comment(line_num, recent_lines[-1][1]))
self.comments.append("# config %s" % (config.filename,))
self.stats_stream = []
self.gcode_stream = []
self.mcus = {}
self.mcu = None
self.first_stat_time = self.last_stat_time = None
for line_num, line in recent_lines:
self.parse_line(line_num, line)
self.first_stat_time = self.last_stat_time
def add_comment(self, comment):
if comment is not None:
self.comments.append(comment)
def check_stats_seq(self, ts, line):
# Parse stats
parts = line.split()
mcu = ""
keyparts = {}
for p in parts[2:]:
if p.endswith(':'):
mcu = p
continue
name, val = p.split('=', 1)
keyparts[mcu + name] = val
min_ts = 0
max_ts = 999999999999
for mcu, info in self.mcus.items():
sname = '%s:send_seq' % (mcu,)
rname = '%s:receive_seq' % (mcu,)
if sname not in keyparts:
continue
sseq = int(keyparts[sname])
rseq = int(keyparts[rname])
min_ts = max(min_ts, info.sent_seq_to_time.get(sseq-1, 0),
info.receive_seq_to_time.get(rseq, 0))
max_ts = min(max_ts, info.sent_seq_to_time.get(sseq, 999999999999),
info.receive_seq_to_time.get(rseq+1, 999999999999))
return min(max(ts, min_ts + 0.00000001), max_ts - 0.00000001)
def trans_clock(self, clock, ts):
sample_time, sample_clock, freq = self.mcu.clock_est
exp_clock = int(sample_clock + (ts - sample_time) * freq)
ext_clock = add_high_bits(clock, exp_clock, 0xffffffff)
return sample_time + (ext_clock - sample_clock) / freq
def annotate(self, line, seq, ts):
if seq is not None:
line = repl_seq_r.sub(r"\g<0>(%d)" % (seq,), line)
def clock_update(m):
return m.group(0)[:-1] + "(%.6f) " % (
self.trans_clock(int(m.group('clock')), ts),)
line = repl_clock_r.sub(clock_update, line)
return line
def add_line(self, line_num, line):
self.parse_line(line_num, line)
if (self.first_stat_time is not None
and self.last_stat_time > self.first_stat_time + 5.):
self.finalize()
return False
if (line.startswith('Git version') or line.startswith('Start printer at')
or line == '===== Config file ====='):
self.finalize()
return False
return True
def parse_line(self, line_num, line):
m = sent_r.match(line)
if m is not None:
shortseq = int(m.group('shortseq'), 16)
seq = (self.mcu.shutdown_seq + int(m.group('count'))
- self.mcu.send_count)
seq = add_high_bits(shortseq, seq, 0xf)
ts = float(m.group('time'))
esttime = float(m.group('esttime'))
self.mcu.sent_time_to_seq[(esttime, seq & 0xf)] = seq
self.mcu.sent_seq_to_time[seq] = ts
line = self.annotate(line, seq, ts)
self.mcu.sent_stream.append((ts, line_num, line))
return
m = receive_r.match(line)
if m is not None:
shortseq = int(m.group('shortseq'), 16)
ts = float(m.group('time'))
esttime = float(m.group('esttime'))
seq = self.mcu.sent_time_to_seq.get((esttime, (shortseq - 1) & 0xf))
if seq is not None:
self.mcu.receive_seq_to_time[seq + 1] = ts
line = self.annotate(line, seq, ts)
self.mcu.receive_stream.append((ts, line_num, line))
return
m = gcode_r.match(line)
if m is not None:
ts = float(m.group('time'))
self.gcode_stream.append((ts, line_num, line))
return
m = stats_r.match(line)
if m is not None:
ts = float(m.group('time'))
self.last_stat_time = ts
if self.first_stat_time is None:
self.first_stat_time = ts
self.stats_stream.append((ts, line_num, line))
return
m = mcu_r.match(line)
if m is not None:
mcu = m.group('mcu')
self.mcu = self.mcus.setdefault(mcu, self.mcu_info())
m = clock_r.match(line)
if m is not None:
st = float(m.group('st'))
sc = int(m.group('sc'))
f = float(m.group('f'))
self.mcu.clock_est = (st, sc, f)
m = serial_dump_r.match(line)
if m is not None:
self.mcu.shutdown_seq = int(m.group('rseq'))
m = send_dump_r.match(line)
if m is not None:
self.mcu.send_count = int(m.group('count'))
m = receive_dump_r.match(line)
if m is not None:
self.mcu.receive_count = int(m.group('count'))
self.stats_stream.append((None, line_num, line))
def finalize(self):
# Ignore old stats
stream_ts = [i[0] for mcu in self.mcus.values()
for i in mcu.sent_stream + mcu.receive_stream]
if not stream_ts:
return
min_stream_ts = min(stream_ts)
max_stream_ts = max(stream_ts)
for i, info in enumerate(self.stats_stream):
if info[0] is not None and info[0] >= min_stream_ts - 5.:
del self.stats_stream[:i]
break
# Improve accuracy of stats timestamps
last_ts = self.stats_stream[0][0]
for i, (ts, line_num, line) in enumerate(self.stats_stream):
if ts is not None:
last_ts = self.check_stats_seq(ts, line)
elif line_num >= self.shutdown_line_num and last_ts <= max_stream_ts:
last_ts = max_stream_ts + 0.00000001
self.stats_stream[i] = (last_ts, line_num, line)
# Make sure no timestamp goes backwards
streams = ([self.stats_stream, self.gcode_stream] +
[mcu.sent_stream for mcu in self.mcus.values()] +
[mcu.receive_stream for mcu in self.mcus.values()])
for s in streams:
for i in range(1, len(s)):
if s[i-1][0] > s[i][0]:
s[i] = (s[i-1][0], s[i][1], s[i][2])
# Produce output sorted by timestamp
out = [i for s in streams for i in s]
out.sort()
out = [i[2] for i in out]
f = open(self.filename, 'wb')
f.write('\n'.join(self.comments + out))
f.close()
######################################################################
# Startup
######################################################################
def main():
logname = sys.argv[1]
last_git = last_start = None
configs = {}
handler = None
recent_lines = collections.deque([], 200)
# Parse log file
f = open(logname, 'rb')
for line_num, line in enumerate(f):
line = line.rstrip()
line_num += 1
recent_lines.append((line_num, line))
if handler is not None:
ret = handler.add_line(line_num, line)
if ret:
continue
recent_lines.clear()
handler = None
if line.startswith('Git version'):
last_git = format_comment(line_num, line)
elif line.startswith('Start printer at'):
last_start = format_comment(line_num, line)
elif line == '===== Config file =====':
handler = GatherConfig(configs, line_num, recent_lines, logname)
handler.add_comment(last_git)
handler.add_comment(last_start)
elif 'shutdown: ' in line or line.startswith('Dumping '):
handler = GatherShutdown(configs, line_num, recent_lines, logname)
handler.add_comment(last_git)
handler.add_comment(last_start)
if handler is not None:
handler.finalize()
# Write found config files
for cfg in configs.values():
cfg.write_file()
if __name__ == '__main__':
main()

View File

@@ -22,6 +22,7 @@ def main():
continue
args = dict([p.split('=', 1) for p in parts[1:]])
if parts[0] == 'config_stepper':
# steppers[oid] = [dir_cmds, dir, queue_cmds, pos steps, neg steps]
steppers[args['oid']] = [0, 0, 0, 0, 0]
elif parts[0] == 'set_next_step_dir':
so = steppers[args['oid']]

44
scripts/travis-build.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/bin/bash
# Test script for travis-ci.org continuous integration.
# Stop script early on any error; check variables; be verbose
set -eux
# Paths to tools installed by travis-install.sh
export PATH=${PWD}/gcc-arm-none-eabi-7-2017-q4-major/bin:${PATH}
PYTHON=${PWD}/python-env/bin/python
######################################################################
# Run compile tests for several different MCU types
######################################################################
DICTDIR=${PWD}/dict
mkdir -p ${DICTDIR}
for TARGET in test/configs/*.config ; do
echo "travis_fold:start:mcu_compile $TARGET"
echo "=============== Test compile $TARGET"
make clean
make distclean
unset CC
cp ${TARGET} .config
make olddefconfig
make V=1
cp out/klipper.dict ${DICTDIR}/$(basename ${TARGET} .config).dict
echo "travis_fold:end:mcu_compile $TARGET"
done
######################################################################
# Verify klippy host software
######################################################################
HOSTDIR=${PWD}/hosttest
mkdir -p ${HOSTDIR}
echo "travis_fold:start:klippy"
echo "=============== Test invoke klippy"
$PYTHON klippy/klippy.py config/example.cfg -i /dev/null -o ${HOSTDIR}/output -v -d ${DICTDIR}/atmega2560-16mhz.dict
$PYTHON klippy/parsedump.py ${DICTDIR}/atmega2560-16mhz.dict ${HOSTDIR}/output > ${HOSTDIR}/output-parsed
echo "travis_fold:end:klippy"

33
scripts/travis-install.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# Build setup script for travis-ci.org continuous integration testing.
# See travis-build.sh for the actual test steps.
# Stop script early on any error; check variables; be verbose
set -eux
######################################################################
# Install embedded arm gcc
######################################################################
echo "=============== Install embedded arm gcc"
GCC_ARM_URL="https://developer.arm.com/-/media/Files/downloads/gnu-rm/7-2017q4/gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2"
GCC_ARM_SHA="96a029e2ae130a1210eaa69e309ea40463028eab18ba19c1086e4c2dafe69a6a gcc-arm-none-eabi-7-2017-q4-major-linux.tar.bz2"
GCC_ARM_FILE="$(basename ${GCC_ARM_URL})"
wget "$GCC_ARM_URL"
FOUND_SHA=`sha256sum "$GCC_ARM_FILE"`
if [ "$FOUND_SHA" != "$GCC_ARM_SHA" ]; then
echo "ERROR: Mismatch on gcc arm sha256"
exit -1
fi
tar xf "$GCC_ARM_FILE"
######################################################################
# Create python virtualenv environment
######################################################################
echo "=============== Install python virtualenv"
virtualenv python-env
./python-env/bin/pip install cffi==1.6.0 pyserial==3.2.1 greenlet==0.4.10

View File

@@ -36,6 +36,9 @@ config HAVE_GPIO_SPI
config HAVE_GPIO_HARD_PWM
bool
default n
config HAVE_USER_INTERFACE
bool
default n
config NO_UNSTEP_DELAY
# Slow micro-controllers do not require a delay before returning a

View File

@@ -5,3 +5,4 @@ src-$(CONFIG_HAVE_GPIO) += gpiocmds.c stepper.c endstop.c
src-$(CONFIG_HAVE_GPIO_ADC) += adccmds.c
src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c
src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c
src-$(CONFIG_HAVE_USER_INTERFACE) += lcd_st7920.c lcd_hd44780.c

View File

@@ -9,6 +9,7 @@ config AVR_SELECT
select HAVE_GPIO_ADC
select HAVE_GPIO_SPI
select HAVE_GPIO_HARD_PWM
select HAVE_USER_INTERFACE
select NO_UNSTEP_DELAY
config BOARD_DIRECTORY
@@ -23,6 +24,8 @@ choice
bool "atmega1280"
config MACH_at90usb1286
bool "at90usb1286"
config MACH_at90usb646
bool "at90usb646"
config MACH_atmega1284p
bool "atmega1284p"
config MACH_atmega644p
@@ -40,9 +43,17 @@ config MCU
default "atmega1284p" if MACH_atmega1284p
default "atmega644p" if MACH_atmega644p
default "at90usb1286" if MACH_at90usb1286
default "at90usb646" if MACH_at90usb646
default "atmega1280" if MACH_atmega1280
default "atmega2560" if MACH_atmega2560
config AVRDUDE_PROTOCOL
string
default "wiring" if MACH_atmega2560
default "avr109" if MACH_at90usb1286
default "avr109" if MACH_at90usb646
default "arduino"
choice
prompt "Processor speed"
config AVR_FREQ_20000000
@@ -62,7 +73,7 @@ config CLOCK_FREQ
config CLEAR_PRESCALER
bool "Manually clear the CPU prescaler field at startup"
depends on MACH_at90usb1286
depends on MACH_at90usb1286 || MACH_at90usb646
default y
help
Some AVR chips ship with a "clock prescaler" that causes the
@@ -85,7 +96,7 @@ config AVR_WATCHDOG
default y
config AVR_USBSERIAL
bool "Use USB for communication (instead of serial)"
depends on MACH_at90usb1286
depends on MACH_at90usb1286 || MACH_at90usb646
default y
config AVR_SERIAL
depends on !AVR_USBSERIAL

View File

@@ -24,6 +24,6 @@ $(OUT)klipper.elf.hex: $(OUT)klipper.elf
$(Q)$(OBJCOPY) -j .text -j .data -O ihex $< $@
flash: $(OUT)klipper.elf.hex
@echo " Flashing $(FLASH_DEVICE) via avrdude"
@echo " Flashing $^ to $(FLASH_DEVICE) via avrdude"
$(Q)if [ -z $(FLASH_DEVICE) ]; then echo "Please specify FLASH_DEVICE"; exit 1; fi
$(Q)avrdude -p$(CONFIG_MCU) -cwiring -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"
$(Q)avrdude -p$(CONFIG_MCU) -c$(CONFIG_AVRDUDE_PROTOCOL) -P"$(FLASH_DEVICE)" -D -U"flash:w:$(OUT)klipper.elf.hex:i"

View File

@@ -152,7 +152,7 @@ static const uint8_t pwm_pins[ARRAY_SIZE(pwm_regs)] PROGMEM = {
# ifdef OCR3A
GPIO('B', 6), GPIO('B', 7),
# endif
#elif CONFIG_MACH_at90usb1286
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646
GPIO('B', 7), GPIO('D', 0),
GPIO('B', 5), GPIO('B', 6), GPIO('B', 7),
GPIO('B', 4), GPIO('D', 1),
@@ -186,20 +186,20 @@ gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val)
uint8_t flags = READP(p->flags), cs;
if (flags & GP_AFMT) {
switch (cycle_time) {
case 0 ... 8*510L - 1: cs = 1; break;
case 8*510L ... 32*510L - 1: cs = 2; break;
case 32*510L ... 64*510L - 1: cs = 3; break;
case 64*510L ... 128*510L - 1: cs = 4; break;
case 128*510L ... 256*510L - 1: cs = 5; break;
case 256*510L ... 1024*510L - 1: cs = 6; break;
case 0 ... (1+8) * 510L / 2 - 1: cs = 1; break;
case (1+8) * 510L / 2 ... (8+32) * 510L / 2 - 1: cs = 2; break;
case (8+32) * 510L / 2 ... (32+64) * 510L / 2 - 1: cs = 3; break;
case (32+64) * 510L / 2 ... (64+128) * 510L / 2 - 1: cs = 4; break;
case (64+128) * 510L / 2 ... (128+256) * 510L / 2 - 1: cs = 5; break;
case (128+256) * 510L / 2 ... (256+1024) * 510L / 2 - 1: cs = 6; break;
default: cs = 7; break;
}
} else {
switch (cycle_time) {
case 0 ... 8*510L - 1: cs = 1; break;
case 8*510L ... 64*510L - 1: cs = 2; break;
case 64*510L ... 256*510L - 1: cs = 3; break;
case 256*510L ... 1024*510L - 1: cs = 4; break;
case 0 ... (1+8) * 510L / 2 - 1: cs = 1; break;
case (1+8) * 510L / 2 ... (8+64) * 510L / 2 - 1: cs = 2; break;
case (8+64) * 510L / 2 ... (64+256) * 510L / 2 - 1: cs = 3; break;
case (64+256) * 510L / 2 ... (256+1024) * 510L / 2 - 1: cs = 4; break;
default: cs = 5; break;
}
}
@@ -252,7 +252,7 @@ static const uint8_t adc_pins[] PROGMEM = {
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
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
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646
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
@@ -280,7 +280,7 @@ gpio_adc_setup(uint8_t pin)
}
// Enable ADC
ADCSRA |= (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
ADCSRA = (1<<ADPS0)|(1<<ADPS1)|(1<<ADPS2)|(1<<ADEN);
// Disable digital input for this pin
#ifdef DIDR2
@@ -316,7 +316,7 @@ gpio_adc_sample(struct gpio_adc g)
#if defined(ADCSRB) && defined(MUX5)
// the MUX5 bit of ADCSRB selects whether we're reading from channels
// 0 to 7 (MUX5 low) or 8 to 15 (MUX5 high).
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((g.chan >> 3) & 0x01) << MUX5);
ADCSRB = ((g.chan >> 3) & 0x01) << MUX5;
#endif
ADMUX = ADMUX_DEFAULT | (g.chan & 0x07);
@@ -352,7 +352,7 @@ gpio_adc_cancel_sample(struct gpio_adc g)
static const uint8_t SS = GPIO('B', 2), SCK = GPIO('B', 5), MOSI = GPIO('B', 3);
#elif CONFIG_MACH_atmega644p || CONFIG_MACH_atmega1284p
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
#elif CONFIG_MACH_at90usb1286 || CONFIG_MACH_at90usb646 || CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560
static const uint8_t SS = GPIO('B', 0), SCK = GPIO('B', 1), MOSI = GPIO('B', 2);
#endif

View File

@@ -93,12 +93,13 @@ DECL_SHUTDOWN(timer_reset);
void
timer_init(void)
{
irqstatus_t flag = irq_save();
// no outputs
TCCR1A = 0;
// Normal Mode
TCCR1B = 1<<CS10;
// Setup for first irq
irqstatus_t flag = irq_save();
TCNT1 = 0;
timer_kick();
timer_repeat_set(timer_get() + 50);
timer_reset();

View File

@@ -142,7 +142,7 @@ soft_pwm_load_event(struct timer *timer)
s->flags = flags;
gpio_out_write(s->pin, flags & SPF_ON);
if (!(flags & SPF_TOGGLING)) {
// Pin is in an always on (value=255) or always off (value=0) state
// Pin is in an always on (value=256) or always off (value=0) state
if (!(flags & SPF_CHECK_END))
return SF_DONE;
s->timer.waketime = s->end_time = s->end_time + s->max_duration;
@@ -199,7 +199,7 @@ command_schedule_soft_pwm_out(uint32_t *args)
s->end_time = time;
s->next_on_duration = next_on_duration;
s->next_off_duration = next_off_duration;
s->flags |= next_flags;
s->flags = (s->flags & 0xf) | next_flags;
if (s->flags & SPF_TOGGLING && timer_is_before(s->timer.waketime, time)) {
// soft_pwm_toggle_event() will schedule a load event when ready
} else {

132
src/lcd_hd44780.c Normal file
View File

@@ -0,0 +1,132 @@
// Commands for sending messages to a 4-bit hd44780 lcd driver
//
// Copyright (C) 2018 Kevin O'Connor <kevin@koconnor.net>
//
// This file may be distributed under the terms of the GNU GPLv3 license.
#include "basecmd.h" // oid_alloc
#include "board/gpio.h" // gpio_out_write
#include "board/irq.h" // irq_disable
#include "board/misc.h" // timer_from_us
#include "command.h" // DECL_COMMAND
#include "sched.h" // DECL_SHUTDOWN
struct hd44780 {
uint32_t last_cmd_time, cmd_wait_ticks;
uint8_t last;
struct gpio_out rs, e, d4, d5, d6, d7;
};
/****************************************************************
* Transmit functions
****************************************************************/
// Write 4 bits to the hd44780 using the 4bit parallel interface
static __always_inline void
hd44780_xmit_bits(uint8_t toggle, struct gpio_out e, struct gpio_out d4
, struct gpio_out d5, struct gpio_out d6, struct gpio_out d7)
{
gpio_out_toggle(e);
if (toggle & 0x10)
gpio_out_toggle(d4);
if (toggle & 0x20)
gpio_out_toggle(d5);
if (toggle & 0x40)
gpio_out_toggle(d6);
if (toggle & 0x80)
gpio_out_toggle(d7);
gpio_out_toggle(e);
}
// Transmit 8 bits to the chip
static void
hd44780_xmit_byte(struct hd44780 *h, uint8_t data)
{
struct gpio_out e = h->e, d4 = h->d4, d5 = h->d5, d6 = h->d6, d7 = h->d7;
hd44780_xmit_bits(h->last ^ data, e, d4, d5, d6, d7);
h->last = data << 4;
hd44780_xmit_bits(data ^ h->last, e, d4, d5, d6, d7);
}
// Transmit a series of bytes to the chip
static void
hd44780_xmit(struct hd44780 *h, uint8_t len, uint8_t *data)
{
uint32_t last_cmd_time=h->last_cmd_time, cmd_wait_ticks=h->cmd_wait_ticks;
while (len--) {
uint8_t b = *data++;
while (timer_read_time() - last_cmd_time < cmd_wait_ticks)
irq_poll();
hd44780_xmit_byte(h, b);
last_cmd_time = timer_read_time();
}
h->last_cmd_time = last_cmd_time;
}
/****************************************************************
* Interface
****************************************************************/
void
command_config_hd44780(uint32_t *args)
{
struct hd44780 *h = oid_alloc(args[0], command_config_hd44780, sizeof(*h));
h->rs = gpio_out_setup(args[1], 0);
h->e = gpio_out_setup(args[2], 0);
h->d4 = gpio_out_setup(args[3], 0);
h->d5 = gpio_out_setup(args[4], 0);
h->d6 = gpio_out_setup(args[5], 0);
h->d7 = gpio_out_setup(args[6], 0);
// Calibrate cmd_wait_ticks
irq_disable();
uint32_t start = timer_read_time();
hd44780_xmit_byte(h, 0);
uint32_t end = timer_read_time();
irq_enable();
uint32_t diff = end - start, delay_ticks = args[7];
if (delay_ticks > diff)
h->cmd_wait_ticks = delay_ticks - diff;
}
DECL_COMMAND(command_config_hd44780,
"config_hd44780 oid=%c rs_pin=%u e_pin=%u"
" d4_pin=%u d5_pin=%u d6_pin=%u d7_pin=%u delay_ticks=%u");
void
command_hd44780_send_cmds(uint32_t *args)
{
struct hd44780 *h = oid_lookup(args[0], command_config_hd44780);
gpio_out_write(h->rs, 0);
uint8_t len = args[1], *cmds = (void*)(size_t)args[2];
hd44780_xmit(h, len, cmds);
}
DECL_COMMAND(command_hd44780_send_cmds, "hd44780_send_cmds oid=%c cmds=%*s");
void
command_hd44780_send_data(uint32_t *args)
{
struct hd44780 *h = oid_lookup(args[0], command_config_hd44780);
gpio_out_write(h->rs, 1);
uint8_t len = args[1], *data = (void*)(size_t)args[2];
hd44780_xmit(h, len, data);
}
DECL_COMMAND(command_hd44780_send_data, "hd44780_send_data oid=%c data=%*s");
void
hd44780_shutdown(void)
{
uint8_t i;
struct hd44780 *h;
foreach_oid(i, h, command_config_hd44780) {
gpio_out_write(h->rs, 0);
gpio_out_write(h->e, 0);
gpio_out_write(h->d4, 0);
gpio_out_write(h->d5, 0);
gpio_out_write(h->d6, 0);
gpio_out_write(h->d7, 0);
h->last = 0;
}
}
DECL_SHUTDOWN(hd44780_shutdown);

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