From ec7990796a1a554869452530f84a91dff9f58c43 Mon Sep 17 00:00:00 2001 From: Kevin O'Connor Date: Mon, 21 Aug 2017 11:25:26 -0400 Subject: [PATCH] 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 --- klippy/fan.py | 10 ++-- klippy/heater.py | 18 +++--- klippy/klippy.py | 12 ++-- klippy/mcu.py | 144 +++++++++++++++++++++++++--------------------- klippy/pins.py | 59 ++++++++++++++++++- klippy/stepper.py | 23 ++++---- 6 files changed, 166 insertions(+), 100 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 # # 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 +# Copyright (C) 2016,2017 Kevin O'Connor # # 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 build_config(self): - self._mcu_freq = self._mcu.get_mcu_freq() - self._query_cmd = self._mcu.lookup_command( - "query_analog_in oid=%c clock=%u sample_ticks=%u sample_count=%c" - " rest_ticks=%u min_value=%hu max_value=%hu") - def set_minmax(self, sample_time, sample_count, minval=0., maxval=1.): + 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 _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 +# Copyright (C) 2016,2017 Kevin O'Connor # # 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 +# Copyright (C) 2016,2017 Kevin O'Connor # # 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(