aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2017-08-21 11:25:26 -0400
committerKevin O'Connor <kevin@koconnor.net>2017-08-25 20:38:55 -0400
commitec7990796a1a554869452530f84a91dff9f58c43 (patch)
tree5cb3288c32143800c796892bc2806e7874cfcdcb
parent268834e4aedac2e435e778802353e767d6b8abe3 (diff)
downloadkutter-ec7990796a1a554869452530f84a91dff9f58c43.tar.gz
kutter-ec7990796a1a554869452530f84a91dff9f58c43.tar.xz
kutter-ec7990796a1a554869452530f84a91dff9f58c43.zip
pins: Support registering arbitrary chips that supply configurable pins
Allow multiple chips to provide pin mappings (not just the main mcu chip). Move the pin parsing from the mcu.py code to pins.py and support mapping from pin descriptions to their corresponding chips and parameters. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
-rw-r--r--klippy/fan.py10
-rw-r--r--klippy/heater.py18
-rw-r--r--klippy/klippy.py12
-rw-r--r--klippy/mcu.py142
-rw-r--r--klippy/pins.py59
-rw-r--r--klippy/stepper.py23
6 files changed, 165 insertions, 99 deletions
diff --git a/klippy/fan.py b/klippy/fan.py
index 6a5766ef..a46dc24e 100644
--- a/klippy/fan.py
+++ b/klippy/fan.py
@@ -3,8 +3,7 @@
# 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
+import extruder, pins
FAN_MIN_TIME = 0.1
PWM_CYCLE_TIME = 0.010
@@ -15,9 +14,10 @@ class PrinterFan:
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.)
- pin = config.get('pin')
- hard_pwm = config.getint('hard_pwm', 0)
- self.mcu_fan = printer.mcu.create_pwm(pin, PWM_CYCLE_TIME, hard_pwm, 0.)
+ 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_pwm(self, mcu_time, value):
value = max(0., min(self.max_power, value))
if value == self.last_fan_value:
diff --git a/klippy/heater.py b/klippy/heater.py
index be8d7252..11f88107 100644
--- a/klippy/heater.py
+++ b/klippy/heater.py
@@ -4,6 +4,7 @@
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging, threading
+import pins
######################################################################
@@ -121,19 +122,18 @@ class PrinterHeater:
algos = {'watermark': ControlBangBang, 'pid': ControlPID}
algo = config.getchoice('control', algos)
heater_pin = config.get('heater_pin')
- sensor_pin = config.get('sensor_pin')
if algo is ControlBangBang and self.max_power == 1.:
- self.mcu_pwm = printer.mcu.create_digital_out(
- heater_pin, MAX_HEAT_TIME)
+ self.mcu_pwm = pins.setup_pin(printer, 'digital_out', heater_pin)
else:
- self.mcu_pwm = printer.mcu.create_pwm(
- heater_pin, PWM_CYCLE_TIME, 0, MAX_HEAT_TIME)
- self.mcu_adc = printer.mcu.create_adc(sensor_pin)
+ self.mcu_pwm = pins.setup_pin(printer, 'pwm', heater_pin)
+ 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),
self.sensor.calc_adc(self.max_temp)]
- self.mcu_adc.set_minmax(SAMPLE_TIME, SAMPLE_COUNT,
- minval=min(adc_range), maxval=max(adc_range))
- self.mcu_adc.set_adc_callback(REPORT_TIME, self.adc_callback)
+ self.mcu_adc.setup_minmax(SAMPLE_TIME, SAMPLE_COUNT,
+ minval=min(adc_range), maxval=max(adc_range))
+ self.mcu_adc.setup_adc_callback(REPORT_TIME, self.adc_callback)
self.control = algo(self, config)
# pwm caching
self.next_pwm_time = 0.
diff --git a/klippy/klippy.py b/klippy/klippy.py
index 61168609..d733c3a8 100644
--- a/klippy/klippy.py
+++ b/klippy/klippy.py
@@ -1,12 +1,12 @@
#!/usr/bin/env python2
# Main code for host side printer firmware
#
-# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import sys, optparse, ConfigParser, logging, time, threading
-import gcode, toolhead, util, mcu, fan, heater, extruder, reactor, queuelogger
-import msgproto
+import util, reactor, queuelogger, msgproto, gcode
+import pins, mcu, extruder, fan, heater, toolhead
message_ready = "Printer is ready"
@@ -170,11 +170,11 @@ class Printer:
config_file,))
if self.bglogger is not None:
ConfigLogger(self.fileconfig, self.bglogger)
- self.mcu = mcu.MCU(self, ConfigWrapper(self, 'mcu'))
# Create printer components
config = ConfigWrapper(self, 'printer')
- for m in [extruder, fan, heater, toolhead]:
+ for m in [pins, mcu, extruder, fan, heater, toolhead]:
m.add_printer_objects(self, config)
+ self.mcu = self.objects['mcu']
# 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():
@@ -196,7 +196,7 @@ class Printer:
self.mcu.connect()
self.gcode.set_printer_ready(True)
self.state_message = message_ready
- except ConfigParser.Error as e:
+ except (ConfigParser.Error, pins.error) as e:
logging.exception("Config error")
self.state_message = "%s%s" % (str(e), message_restart)
self.reactor.update_timer(self.stats_timer, self.reactor.NEVER)
diff --git a/klippy/mcu.py b/klippy/mcu.py
index aa35f76d..3190a325 100644
--- a/klippy/mcu.py
+++ b/klippy/mcu.py
@@ -9,24 +9,15 @@ import serialhdl, pins, chelper
class error(Exception):
pass
-def parse_pin_extras(pin, can_pullup=False):
- pullup = invert = 0
- if can_pullup and pin.startswith('^'):
- pullup = 1
- pin = pin[1:].strip()
- if pin.startswith('!'):
- invert = 1
- pin = pin[1:].strip()
- return pin, pullup, invert
-
STEPCOMPRESS_ERROR_RET = -989898989
class MCU_stepper:
- def __init__(self, mcu, step_pin, dir_pin):
+ def __init__(self, mcu, pin_params):
self._mcu = mcu
self._oid = mcu.create_oid(self)
- self._step_pin, pullup, self._invert_step = parse_pin_extras(step_pin)
- self._dir_pin, pullup, self._invert_dir = parse_pin_extras(dir_pin)
+ self._step_pin = pin_params['pin']
+ self._invert_step = pin_params['invert']
+ self._dir_pin = self._invert_dir = None
self._commanded_pos = 0
self._step_dist = self._inv_step_dist = 1.
self._velocity_factor = self._accel_factor = 0.
@@ -36,9 +27,14 @@ class MCU_stepper:
self._ffi_lib = self._stepqueue = None
self.print_to_mcu_time = mcu.print_to_mcu_time
self.system_to_mcu_time = mcu.system_to_mcu_time
- def set_min_stop_interval(self, min_stop_interval):
+ def setup_dir_pin(self, pin_params):
+ if pin_params['chip'] is not self._mcu:
+ raise pins.error("Stepper dir pin must be on same mcu as step pin")
+ self._dir_pin = pin_params['pin']
+ self._invert_dir = pin_params['invert']
+ def setup_min_stop_interval(self, min_stop_interval):
self._min_stop_interval = min_stop_interval
- def set_step_distance(self, step_dist):
+ def setup_step_distance(self, step_dist):
self._step_dist = step_dist
self._inv_step_dist = 1. / step_dist
def build_config(self):
@@ -144,12 +140,13 @@ class MCU_stepper:
class MCU_endstop:
error = error
RETRY_QUERY = 1.000
- def __init__(self, mcu, pin):
+ def __init__(self, mcu, pin_params):
self._mcu = mcu
self._oid = mcu.create_oid(self)
self._steppers = []
- self._pin, self._pullup, self._invert = parse_pin_extras(
- pin, can_pullup=True)
+ self._pin = pin_params['pin']
+ self._pullup = pin_params['pullup']
+ self._invert = pin_params['invert']
self._cmd_queue = mcu.alloc_command_queue()
self._home_cmd = self._query_cmd = None
self._homing = False
@@ -240,23 +237,27 @@ class MCU_endstop:
return self._last_state.get('pin', self._invert) ^ self._invert
class MCU_digital_out:
- def __init__(self, mcu, pin, max_duration):
+ def __init__(self, mcu, pin_params):
self._mcu = mcu
self._oid = mcu.create_oid(self)
- pin, pullup, self._invert = parse_pin_extras(pin)
+ self._pin = pin_params['pin']
+ self._invert = pin_params['invert']
+ self._max_duration = 2.
self._last_clock = 0
self._last_value = None
self._mcu_freq = 0.
self._cmd_queue = mcu.alloc_command_queue()
- mcu.add_config_cmd(
- "config_digital_out oid=%d pin=%s default_value=%d"
- " max_duration=TICKS(%f)" % (
- self._oid, pin, self._invert, max_duration))
self._set_cmd = None
self.print_to_mcu_time = mcu.print_to_mcu_time
self.system_to_mcu_time = mcu.system_to_mcu_time
+ def setup_max_duration(self, max_duration):
+ self._max_duration = max_duration
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
+ self._mcu.add_config_cmd(
+ "config_digital_out oid=%d pin=%s default_value=%d"
+ " max_duration=TICKS(%f)" % (
+ self._oid, self._pin, self._invert, self._max_duration))
self._set_cmd = self._mcu.lookup_command(
"schedule_digital_out oid=%c clock=%u value=%c")
def set_digital(self, mcu_time, value):
@@ -275,37 +276,49 @@ class MCU_digital_out:
self.set_digital(mcu_time, dval)
class MCU_pwm:
- def __init__(self, mcu, pin, cycle_time, hard_cycle_ticks, max_duration):
+ def __init__(self, mcu, pin_params):
self._mcu = mcu
- self._hard_cycle_ticks = hard_cycle_ticks
+ self._hard_pwm = False
+ self._cycle_time = 0.100
+ self._max_duration = 2.
self._oid = mcu.create_oid(self)
- pin, pullup, self._invert = parse_pin_extras(pin)
+ self._pin = pin_params['pin']
+ self._invert = pin_params['invert']
self._last_clock = 0
self._mcu_freq = 0.
self._pwm_max = 0.
self._cmd_queue = mcu.alloc_command_queue()
- if hard_cycle_ticks:
- mcu.add_config_cmd(
- "config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=%d"
- " max_duration=TICKS(%f)" % (
- self._oid, pin, hard_cycle_ticks, self._invert,
- max_duration))
- else:
- mcu.add_config_cmd(
- "config_soft_pwm_out oid=%d pin=%s cycle_ticks=TICKS(%f)"
- " default_value=%d max_duration=TICKS(%f)" % (
- self._oid, pin, cycle_time, self._invert, max_duration))
self._set_cmd = None
self.print_to_mcu_time = mcu.print_to_mcu_time
self.system_to_mcu_time = mcu.system_to_mcu_time
+ def setup_max_duration(self, max_duration):
+ self._max_duration = max_duration
+ def setup_cycle_time(self, cycle_time):
+ 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 build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
- if self._hard_cycle_ticks:
+ if self._hard_pwm:
+ self._mcu.add_config_cmd(
+ "config_pwm_out oid=%d pin=%s cycle_ticks=%d default_value=%d"
+ " max_duration=TICKS(%f)" % (
+ self._oid, self._pin, self._cycle_time, self._invert,
+ self._max_duration))
self._pwm_max = self._mcu.serial.msgparser.get_constant_float(
"PWM_MAX")
self._set_cmd = self._mcu.lookup_command(
"schedule_pwm_out oid=%c clock=%u value=%hu")
else:
+ self._mcu.add_config_cmd(
+ "config_soft_pwm_out oid=%d pin=%s cycle_ticks=TICKS(%f)"
+ " default_value=%d max_duration=TICKS(%f)" % (
+ self._oid, self._pin, self._cycle_time, self._invert,
+ self._max_duration))
self._pwm_max = self._mcu.serial.msgparser.get_constant_float(
"SOFT_PWM_MAX")
self._set_cmd = self._mcu.lookup_command(
@@ -321,8 +334,9 @@ class MCU_pwm:
self._last_clock = clock
class MCU_adc:
- def __init__(self, mcu, pin):
+ def __init__(self, mcu, pin_params):
self._mcu = mcu
+ self._pin = pin_params['pin']
self._oid = mcu.create_oid(self)
self._min_sample = self._max_sample = 0.
self._sample_time = self._report_time = 0.
@@ -332,20 +346,23 @@ class MCU_adc:
self._inv_max_adc = 0.
self._mcu_freq = 0.
self._cmd_queue = mcu.alloc_command_queue()
- mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (self._oid, pin))
self._query_cmd = None
mcu.add_init_callback(self._init_callback)
- self._query_cmd = None
+ def setup_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
+ self._sample_time = sample_time
+ self._sample_count = sample_count
+ self._min_sample = minval
+ self._max_sample = maxval
+ def setup_adc_callback(self, report_time, callback):
+ self._report_time = report_time
+ self._callback = callback
def build_config(self):
self._mcu_freq = self._mcu.get_mcu_freq()
+ self._mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (
+ self._oid, self._pin))
self._query_cmd = self._mcu.lookup_command(
"query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c"
" rest_ticks=%u min_value=%hu max_value=%hu")
- def set_minmax(self, sample_time, sample_count, minval=0., maxval=1.):
- self._sample_time = sample_time
- self._sample_count = sample_count
- self._min_sample = minval
- self._max_sample = maxval
def _init_callback(self):
if not self._sample_count:
return
@@ -370,9 +387,6 @@ class MCU_adc:
last_read_time = (next_clock - self._report_clock) / self._mcu_freq
if self._callback is not None:
self._callback(last_read_time, last_value)
- def set_adc_callback(self, report_time, callback):
- self._report_time = report_time
- self._callback = callback
class MCU:
error = error
@@ -398,7 +412,7 @@ class MCU:
# Config building
if printer.bglogger is not None:
printer.bglogger.set_rollover_info("mcu", None)
- self._config_error = config.error
+ pins.get_printer_pins(printer).register_chip("mcu", self)
self._emergency_stop_cmd = self._reset_cmd = None
self._oids = []
self._config_cmds = []
@@ -564,8 +578,7 @@ class MCU:
updated_cmds.append(pins.update_command(
cmd, self._mcu_freq, pnames))
except:
- raise self._config_error("Unable to translate pin name: %s" % (
- cmd,))
+ raise pins.error("Unable to translate pin name: %s" % (cmd,))
self._config_cmds = updated_cmds
# Calculate config CRC
@@ -617,6 +630,13 @@ class MCU:
for cb in self._init_callbacks:
cb()
# Config creation helpers
+ def setup_pin(self, pin_params):
+ pcs = {'stepper': MCU_stepper, 'endstop': MCU_endstop,
+ 'digital_out': MCU_digital_out, 'pwm': MCU_pwm, 'adc': MCU_adc}
+ pin_type = pin_params['type']
+ if pin_type not in pcs:
+ raise pins.error("pin type %s not supported on mcu" % (pin_type,))
+ return pcs[pin_type](self, pin_params)
def create_oid(self, oid):
self._oids.append(oid)
return len(self._oids) - 1
@@ -634,19 +654,6 @@ class MCU:
return self.serial.msgparser.lookup_command(msgformat)
def create_command(self, msg):
return self.serial.msgparser.create_command(msg)
- # Wrappers for mcu object creation
- def create_stepper(self, step_pin, dir_pin):
- return MCU_stepper(self, step_pin, dir_pin)
- def create_endstop(self, pin):
- return MCU_endstop(self, pin)
- def create_digital_out(self, pin, max_duration=2.):
- return MCU_digital_out(self, pin, max_duration)
- def create_pwm(self, pin, cycle_time, hard_cycle_ticks=0, max_duration=2.):
- if hard_cycle_ticks < 0:
- return MCU_digital_out(self, pin, max_duration)
- return MCU_pwm(self, pin, cycle_time, hard_cycle_ticks, max_duration)
- def create_adc(self, pin):
- return MCU_adc(self, pin)
# Clock syncing
def set_print_start_time(self, eventtime):
clock = self.serial.get_clock(eventtime)
@@ -687,3 +694,6 @@ class MCU:
return self._printer.reactor.monotonic()
def __del__(self):
self.disconnect()
+
+def add_printer_objects(printer, config):
+ printer.add_object('mcu', MCU(printer, config.getsection('mcu')))
diff --git a/klippy/pins.py b/klippy/pins.py
index 9d3da5e6..cdfdede0 100644
--- a/klippy/pins.py
+++ b/klippy/pins.py
@@ -1,11 +1,16 @@
# Pin name to pin number definitions
#
-# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import re
+
+######################################################################
+# Hardware pin names
+######################################################################
+
def port_pins(port_count, bit_count=8):
pins = {}
for port in range(port_count):
@@ -142,12 +147,12 @@ def update_map_beaglebone(pins, mcu):
######################################################################
-# External commands
+# Command translation
######################################################################
# Obtains the pin mappings
def get_pin_map(mcu, mapping_name=None):
- pins = MCU_PINS.get(mcu, {})
+ pins = dict(MCU_PINS.get(mcu, {}))
if mapping_name == 'arduino':
update_map_arduino(pins, mcu)
elif mapping_name == 'beaglebone':
@@ -163,3 +168,51 @@ def update_command(cmd, mcu_freq, pmap):
def ticks_fixup(m):
return str(int(mcu_freq * float(m.group('ticks'))))
return re_ticks.sub(ticks_fixup, re_pin.sub(pin_fixup, cmd))
+
+
+######################################################################
+# Pin to chip mapping
+######################################################################
+
+class error(Exception):
+ pass
+
+class PrinterPins:
+ error = error
+ def __init__(self):
+ self.chips = {}
+ def parse_pin_desc(self, pin_desc, can_invert=False, can_pullup=False):
+ pullup = invert = 0
+ if can_pullup and pin_desc.startswith('^'):
+ pullup = 1
+ pin_desc = pin_desc[1:].strip()
+ if can_invert and pin_desc.startswith('!'):
+ invert = 1
+ pin_desc = pin_desc[1:].strip()
+ if ':' not in pin_desc:
+ chip_name, pin = 'mcu', pin_desc
+ else:
+ chip_name, pin = [s.strip() for s in pin_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}
+ def register_chip(self, chip_name, chip):
+ chip_name = chip_name.strip()
+ if chip_name in self.chips:
+ raise error("Duplicate chip name '%s'" % (chip_name,))
+ self.chips[chip_name] = chip
+
+def add_printer_objects(printer, config):
+ printer.add_object('pins', PrinterPins())
+
+def get_printer_pins(printer):
+ return printer.objects['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)
diff --git a/klippy/stepper.py b/klippy/stepper.py
index f5085bc0..c97c8e4d 100644
--- a/klippy/stepper.py
+++ b/klippy/stepper.py
@@ -1,10 +1,10 @@
# Printer stepper support
#
-# Copyright (C) 2016 Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2016,2017 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import math, logging
-import homing
+import homing, pins
class PrinterStepper:
def __init__(self, printer, config, name):
@@ -13,14 +13,17 @@ class PrinterStepper:
self.step_dist = config.getfloat('step_distance', above=0.)
self.inv_step_dist = 1. / self.step_dist
self.min_stop_interval = 0.
- step_pin = config.get('step_pin')
- dir_pin = config.get('dir_pin')
- self.mcu_stepper = printer.mcu.create_stepper(step_pin, dir_pin)
- self.mcu_stepper.set_step_distance(self.step_dist)
+ 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)
+ self.mcu_stepper.setup_dir_pin(dir_pin_params)
+ self.mcu_stepper.setup_step_distance(self.step_dist)
enable_pin = config.get('enable_pin', None)
if enable_pin is not None:
- self.mcu_enable = printer.mcu.create_digital_out(enable_pin, 0)
+ self.mcu_enable = pins.setup_pin(printer, 'digital_out', enable_pin)
+ self.mcu_enable.setup_max_duration(0.)
self.need_motor_enable = True
def _dist_to_time(self, dist, start_velocity, accel):
# Calculate the time it takes to travel a distance with constant accel
@@ -33,7 +36,7 @@ class PrinterStepper:
second_last_step_time = self._dist_to_time(
2. * self.step_dist, max_halt_velocity, max_accel)
min_stop_interval = second_last_step_time - last_step_time
- self.mcu_stepper.set_min_stop_interval(min_stop_interval)
+ self.mcu_stepper.setup_min_stop_interval(min_stop_interval)
def motor_enable(self, move_time, enable=0):
if enable and self.need_motor_enable:
mcu_time = self.mcu_stepper.print_to_mcu_time(move_time)
@@ -48,8 +51,8 @@ class PrinterHomingStepper(PrinterStepper):
def __init__(self, printer, config, name):
PrinterStepper.__init__(self, printer, config, name)
- endstop_pin = config.get('endstop_pin', None)
- self.mcu_endstop = printer.mcu.create_endstop(endstop_pin)
+ self.mcu_endstop = pins.setup_pin(
+ printer, 'endstop', config.get('endstop_pin'))
self.mcu_endstop.add_stepper(self.mcu_stepper)
self.position_min = config.getfloat('position_min', 0.)
self.position_max = config.getfloat(