diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 935a3d52..7b943218 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -122,3 +122,8 @@ # its highest resistance, and then the 'channel_x' parameters can be # specified using the desired amperage value for the stepper. The # default is to not scale the 'channel_x' parameters. + + +# Replicape support - see the generic-replicape.cfg file for further +# details. +#[replicape] diff --git a/config/generic-replicape.cfg b/config/generic-replicape.cfg index 4095eace..2677973b 100644 --- a/config/generic-replicape.cfg +++ b/config/generic-replicape.cfg @@ -1,14 +1,15 @@ -# This file contains an example configuration for the Beaglebone PRU -# micro-controller. - -# THIS FILE HAS NOT BEEN TESTED - PROCEED WITH CAUTION! +# This file contains an example configuration for the Replicape rev B3 +# board. To use this config, one must compile and install the +# micro-controller code for the "Beaglebone PRU", and then compile and +# install the micro-controller code a second time for a "Linux +# process". # See the example.cfg file for a description of available parameters. [stepper_x] step_pin: P8_17 dir_pin: P8_26 -enable_pin: !P9_41 +enable_pin: replicape:stepper_x_enable step_distance: .0125 endstop_pin: ^P9_25 position_endstop: 0 @@ -18,7 +19,7 @@ homing_speed: 50 [stepper_y] step_pin: P8_12 dir_pin: P8_19 -enable_pin: !P9_41 +enable_pin: replicape:stepper_y_enable step_distance: .0125 endstop_pin: ^P9_23 position_endstop: 0 @@ -28,42 +29,39 @@ homing_speed: 50 [stepper_z] step_pin: P8_13 dir_pin: P8_14 -enable_pin: !P9_41 +enable_pin: replicape:stepper_z_enable step_distance: 0.00025 endstop_pin: ^P9_13 position_endstop: 0 position_max: 200 -# XXX - Extruder heater hooked up to i2c mosfet -#[extruder] -#step_pin: P9_12 -#dir_pin: P8_15 -#enable_pin: !P9_41 -#step_distance: .002 -#nozzle_diameter: 0.400 -#filament_diameter: 1.750 -#heater_pin: ? -#sensor_type: EPCOS 100K B57560G104F -#sensor_pin: P9_39 -#control: pid -#pid_Kp: 22.2 -#pid_Ki: 1.08 -#pid_Kd: 114 -#min_temp: 0 -#max_temp: 250 +[extruder] +step_pin: P9_12 +dir_pin: P8_15 +enable_pin: replicape:stepper_e_enable +step_distance: .002 +nozzle_diameter: 0.400 +filament_diameter: 1.750 +heater_pin: replicape:power_e +sensor_type: EPCOS 100K B57560G104F +sensor_pin: host:analog4 +control: pid +pid_Kp: 22.2 +pid_Ki: 1.08 +pid_Kd: 114 +min_temp: 0 +max_temp: 250 -# XXX - Bed heater hooked up to i2c mosfet -#[heater_bed] -#heater_pin: ? -#sensor_type: EPCOS 100K B57560G104F -#sensor_pin: P9_40 -#control: watermark -#min_temp: 0 -#max_temp: 130 +[heater_bed] +heater_pin: replicape:power_hotbed +sensor_type: EPCOS 100K B57560G104F +sensor_pin: host:analog6 +control: watermark +min_temp: 0 +max_temp: 130 -# XXX - Fan power hooked up to i2c mosfet -#[fan] -#pin: ? +[fan] +pin: replicape:power_fan0 [mcu] serial: /dev/rpmsg_pru30 @@ -73,5 +71,47 @@ pin_map: beaglebone kinematics: cartesian max_velocity: 300 max_accel: 3000 -max_z_velocity: 5 -max_z_accel: 100 +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 diff --git a/docs/beaglebone.md b/docs/beaglebone.md index 2eea47a8..d3e1b000 100644 --- a/docs/beaglebone.md +++ b/docs/beaglebone.md @@ -71,6 +71,20 @@ make flash sudo service klipper start ``` +For the Replicape, it is also necessary to compile and install the +micro-controller code for a Linux host process. Run "make menuconfig" +a second time and configure it for a "Linux process": +``` +make menuconfig +``` + +Then install this micro-controller code as well: +``` +sudo service klipper stop +make flash +sudo service klipper start +``` + Remaining configuration ======================= diff --git a/klippy/chipmisc.py b/klippy/chipmisc.py index 7e875a4f..de4a5b9c 100644 --- a/klippy/chipmisc.py +++ b/klippy/chipmisc.py @@ -3,7 +3,7 @@ # Copyright (C) 2017 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import pins +import pins, mcu ###################################################################### @@ -57,11 +57,183 @@ class ad5206: "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._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 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 max_duration=%d" % ( + self._oid, self._bus, self._address, self._channel, + cycle_ticks, 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(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 '):