From 9d75c3b0cace034ef8d51be1b38b705db33ac793 Mon Sep 17 00:00:00 2001
From: Kevin O'Connor <kevin@koconnor.net>
Date: Thu, 24 Aug 2017 09:34:31 -0400
Subject: [PATCH] chipmisc: Add support for statically configured output pins

Allow digital and PWM output pins to be setup via new config
sections.  This makes it easier to setup pin configurations.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
---
 config/example-extras.cfg    | 83 ++++++++++++++++++++++++++++++++++++
 config/example.cfg           | 38 +----------------
 config/generic-rambo.cfg     | 25 ++++++-----
 config/makergear-m2-2012.cfg | 22 +++++-----
 klippy/chipmisc.py           | 43 +++++++++++++++++++
 klippy/klippy.py             |  4 +-
 klippy/mcu.py                | 52 ++++++++++++++++------
 7 files changed, 193 insertions(+), 74 deletions(-)
 create mode 100644 config/example-extras.cfg
 create mode 100644 klippy/chipmisc.py

diff --git a/config/example-extras.cfg b/config/example-extras.cfg
new file mode 100644
index 000000000..ec9de90b4
--- /dev/null
+++ b/config/example-extras.cfg
@@ -0,0 +1,83 @@
+# This file serves as documentation for config parameters of
+# 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.
+
+
+# In a multi-extruder printer add an additional extruder section for
+# each additional extruder. The additional extruder sections should be
+# named "extruder1", "extruder2", "extruder3", and so on. See the
+# "extruder" section in example.cfg for a description of available
+# parameters.
+#[extruder1]
+#step_pin: ar36
+#dir_pin: ar34
+#...
+#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
+#   activates some other extruder. It only makes sense to define this
+#   section on multi-extruder printers. The default is to not run any
+#   special G-Code commands on deactivation.
+#activate_gcode:
+#   A list of G-Code commands (one per line) to execute on a G-Code
+#   tool change command (eg, "T0") that activates this extruder. It
+#   only makes sense to define this section on multi-extruder
+#   printers. The default is to not run any special G-Code commands on
+#   activation.
+
+
+# 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.
+#[heater_fan my_nozzle_fan]
+# See the "fan" section for fan configuration parameters.
+#pin: ar4
+# The remaining variables are specific to heater_fan.
+#heater: extruder
+#   Name of the config section defining the heater that this fan is
+#   associated with.  The default is "extruder".
+#heater_temp: 50.0
+#   A temperature (in Celsius) that the heater must drop below before
+#   the fan is disabled. The default is 50 Celsius.
+
+
+# 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.
+#[static_digital_output my_output_pins]
+#pins:
+#   A comma separated list of pins to be set as GPIO output pins. The
+#   pin will be set to a high level unless the pin name is prefaced
+#   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]
+#pin:
+#   The pin to configure as PWM output. This parameter must be
+#   provided.
+#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.
+#cycle_time: 0.100
+#   The amount of time (in seconds) per PWM cycle when using software
+#   based PWM. The default is 0.100 seconds.
+#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.
diff --git a/config/example.cfg b/config/example.cfg
index 4bb805dbc..32ece8359 100644
--- a/config/example.cfg
+++ b/config/example.cfg
@@ -2,7 +2,8 @@
 # copy and edit this file to configure a new cartesian style
 # printer. For delta style printers, see the "example-delta.cfg"
 # file. For corexy/h-bot style printers, see the "example-corexy.cfg"
-# file.
+# file. Only common config sections are described here - see the
+# "example-extras.cfg" file for configuring less common devices.
 
 # DO NOT COPY THIS FILE WITHOUT CAREFULLY READING AND UPDATING IT
 # FIRST. Incorrectly configured parameters may cause damage.
@@ -150,18 +151,6 @@ filament_diameter: 3.500
 #   otherwise cause retraction followed immediately by pressure
 #   buildup. This setting only applies if pressure_advance is
 #   non-zero. The default is 0.010 (10 milliseconds).
-#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
-#   activates some other extruder. It only makes sense to define this
-#   section on multi-extruder printers. The default is to not run any
-#   special G-Code commands on deactivation.
-#activate_gcode:
-#   A list of G-Code commands (one per line) to execute on a G-Code
-#   tool change command (eg, "T0") that activates this extruder. It
-#   only makes sense to define this section on multi-extruder
-#   printers. The default is to not run any special G-Code commands on
-#   activation.
 #
 # The remaining variables describe the extruder heater.
 heater_pin: ar10
@@ -217,15 +206,6 @@ max_temp: 210
 #   Maximum temperature (mcu will shutdown if temperature is above
 #   this value). This parameter must be provided.
 
-# In a multi-extruder printer add an additional extruder section for
-# each additional extruder. The additional extruder sections should be
-# named "extruder1", "extruder2", "extruder3", and so on. See the
-# "extruder" section above for a description of available parameters.
-#[extruder1]
-#step_pin: ar36
-#dir_pin: ar34
-#...
-
 # The heater_bed section describes a heated bed (if present - omit
 # section if not present).
 [heater_bed]
@@ -323,17 +303,3 @@ 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.
-
-# 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.
-#[heater_fan my_nozzle_fan]
-# See the "fan" section for fan configuration parameters.
-#pin: ar4
-# The remaining variables are specific to heater_fan.
-#heater: extruder
-#   Name of the config section defining the heater that this fan is
-#   associated with.  The default is "extruder".
-#heater_temp: 50.0
-#   A temperature (in Celsius) that the heater must drop below before
-#   the fan is disabled. The default is 50 Celsius.
diff --git a/config/generic-rambo.cfg b/config/generic-rambo.cfg
index f1cc0389c..f01eb82ff 100644
--- a/config/generic-rambo.cfg
+++ b/config/generic-rambo.cfg
@@ -77,19 +77,6 @@ pin: PH5
 [mcu]
 serial: /dev/ttyACM0
 custom:
-  # Turn off yellow led
-  set_digital_out pin=PB7 value=0
-  # Stepper micro-step pins
-  set_digital_out pin=PG1 value=1
-  set_digital_out pin=PG0 value=1
-  set_digital_out pin=PK7 value=1
-  set_digital_out pin=PG2 value=1
-  set_digital_out pin=PK6 value=1
-  set_digital_out pin=PK5 value=1
-  set_digital_out pin=PK3 value=1
-  set_digital_out pin=PK4 value=1
-  #set_digital_out pin=PK2 value=1
-  #set_digital_out pin=PK1 value=1
   # Initialize digipot
   send_spi_message pin=PD7 msg=0487 # X = ~0.75A
   send_spi_message pin=PD7 msg=0587 # Y = ~0.75A
@@ -103,3 +90,15 @@ max_velocity: 300
 max_accel: 3000
 max_z_velocity: 5
 max_z_accel: 100
+
+# 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,
+    PK2, PK1
+
+[static_digital_output yellow_led]
+pins: !PB7
diff --git a/config/makergear-m2-2012.cfg b/config/makergear-m2-2012.cfg
index a2b0e89d3..1bf72a54d 100644
--- a/config/makergear-m2-2012.cfg
+++ b/config/makergear-m2-2012.cfg
@@ -77,17 +77,6 @@ hard_pwm: 1
 [mcu]
 serial: /dev/ttyACM0
 custom:
-  # Turn off yellow led
-  set_digital_out pin=PB7 value=0
-  # Stepper micro-step pins
-  set_digital_out pin=PG1 value=1
-  set_digital_out pin=PG0 value=1
-  set_digital_out pin=PK7 value=1
-  set_digital_out pin=PG2 value=1
-  set_digital_out pin=PK6 value=1
-  set_digital_out pin=PK5 value=1
-  set_digital_out pin=PK3 value=1
-  set_digital_out pin=PK4 value=1
   # Initialize digipot
   send_spi_message pin=PD7 msg=0487 # X = ~0.75A
   send_spi_message pin=PD7 msg=0587 # Y = ~0.75A
@@ -101,3 +90,14 @@ max_velocity: 500
 max_accel: 3000
 max_z_velocity: 25
 max_z_accel: 30
+
+# Enable 8 micro-steps on steppers X, Y, Z, E0
+[static_digital_output stepper_config]
+pins:
+    PG1, PG0,
+    PK7, PG2,
+    PK6, PK5,
+    PK3, PK4
+
+[static_digital_output yellow_led]
+pins: !PB7
diff --git a/klippy/chipmisc.py b/klippy/chipmisc.py
new file mode 100644
index 000000000..bdcb59d84
--- /dev/null
+++ b/klippy/chipmisc.py
@@ -0,0 +1,43 @@
+# 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
+
+
+######################################################################
+# 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)
+
+
+######################################################################
+# Setup
+######################################################################
+
+def add_printer_objects(printer, config):
+    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))
diff --git a/klippy/klippy.py b/klippy/klippy.py
index d733c3a8e..f674720be 100644
--- a/klippy/klippy.py
+++ b/klippy/klippy.py
@@ -6,7 +6,7 @@
 # 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, extruder, fan, heater, toolhead
+import pins, mcu, chipmisc, extruder, fan, heater, toolhead
 
 message_ready = "Printer is ready"
 
@@ -172,7 +172,7 @@ class Printer:
             ConfigLogger(self.fileconfig, self.bglogger)
         # Create printer components
         config = ConfigWrapper(self, 'printer')
-        for m in [pins, mcu, extruder, fan, heater, toolhead]:
+        for m in [pins, mcu, chipmisc, 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
diff --git a/klippy/mcu.py b/klippy/mcu.py
index 4d0122864..1132ab42a 100644
--- a/klippy/mcu.py
+++ b/klippy/mcu.py
@@ -14,7 +14,6 @@ STEPCOMPRESS_ERROR_RET = -989898989
 class MCU_stepper:
     def __init__(self, mcu, pin_params):
         self._mcu = mcu
-        self._oid = mcu.create_oid()
         self._step_pin = pin_params['pin']
         self._invert_step = pin_params['invert']
         self._dir_pin = self._invert_dir = None
@@ -23,7 +22,7 @@ class MCU_stepper:
         self._velocity_factor = self._accel_factor = 0.
         self._mcu_position_offset = 0
         self._mcu_freq = self._min_stop_interval = 0.
-        self._reset_cmd = self._get_position_cmd = None
+        self._oid = self._reset_cmd = self._get_position_cmd = None
         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
@@ -39,6 +38,7 @@ class MCU_stepper:
         self._inv_step_dist = 1. / step_dist
     def build_config(self):
         self._mcu_freq = self._mcu.get_mcu_freq()
+        self._oid = self._mcu.create_oid()
         self._velocity_factor = 1. / (self._mcu_freq * self._step_dist)
         self._accel_factor = 1. / (self._mcu_freq**2 * self._step_dist)
         max_error = self._mcu.get_max_stepper_error()
@@ -142,13 +142,12 @@ class MCU_endstop:
     RETRY_QUERY = 1.000
     def __init__(self, mcu, pin_params):
         self._mcu = mcu
-        self._oid = mcu.create_oid()
         self._steppers = []
         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._oid = self._home_cmd = self._query_cmd = None
         self._homing = False
         self._min_query_time = self._mcu_freq = 0.
         self._next_query_clock = self._home_timeout_clock = 0
@@ -160,6 +159,7 @@ class MCU_endstop:
         self._steppers.append(stepper)
     def build_config(self):
         self._mcu_freq = self._mcu.get_mcu_freq()
+        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)))
@@ -236,7 +236,8 @@ class MCU_endstop:
 class MCU_digital_out:
     def __init__(self, mcu, pin_params):
         self._mcu = mcu
-        self._oid = mcu.create_oid()
+        self._oid = None
+        self._static_value = None
         self._pin = pin_params['pin']
         self._invert = pin_params['invert']
         self._max_duration = 2.
@@ -249,8 +250,15 @@ class MCU_digital_out:
         self.system_to_mcu_time = mcu.system_to_mcu_time
     def setup_max_duration(self, max_duration):
         self._max_duration = max_duration
+    def setup_static(self):
+        self._static_value = not self._invert
     def build_config(self):
         self._mcu_freq = self._mcu.get_mcu_freq()
+        if self._static_value is not None:
+            self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
+                self._pin, self._static_value))
+            return
+        self._oid = self._mcu.create_oid()
         self._mcu.add_config_cmd(
             "config_digital_out oid=%d pin=%s default_value=%d"
             " max_duration=TICKS(%f)" % (
@@ -278,7 +286,8 @@ class MCU_pwm:
         self._hard_pwm = False
         self._cycle_time = 0.100
         self._max_duration = 2.
-        self._oid = mcu.create_oid()
+        self._oid = None
+        self._static_value = None
         self._pin = pin_params['pin']
         self._invert = pin_params['invert']
         self._last_clock = 0
@@ -298,26 +307,45 @@ class MCU_pwm:
             return
         self._cycle_time = hard_cycle_ticks
         self._hard_pwm = True
+    def setup_static_pwm(self, value):
+        if self._invert:
+            self._static_value = 1. - value
+        else:
+            self._static_value = value
     def build_config(self):
         self._mcu_freq = self._mcu.get_mcu_freq()
         if self._hard_pwm:
+            self._pwm_max = self._mcu.serial.msgparser.get_constant_float(
+                "PWM_MAX")
+            if self._static_value is not None:
+                value = int(self._static_value * self._pwm_max + 0.5)
+                self._mcu.add_config_cmd(
+                    "set_pwm_out pin=%s cycle_ticks=%d value=%d" % (
+                        self._pin, self._cycle_time, value))
+                return
+            self._oid = self._mcu.create_oid()
             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._pwm_max = self._mcu.serial.msgparser.get_constant_float(
+                "SOFT_PWM_MAX")
+            if self._static_value is not None:
+                if self._static_value != 0. and self._static_value != 1.:
+                    raise pins.error("static value on soft pwm not supported")
+                self._mcu.add_config_cmd("set_digital_out pin=%s value=%d" % (
+                    self._pin, int(self._static_value)))
+                return
+            self._oid = self._mcu.create_oid()
             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(
                 "schedule_soft_pwm_out oid=%c clock=%u value=%hu")
     def set_pwm(self, mcu_time, value):
@@ -334,12 +362,11 @@ class MCU_adc:
     def __init__(self, mcu, pin_params):
         self._mcu = mcu
         self._pin = pin_params['pin']
-        self._oid = mcu.create_oid()
         self._min_sample = self._max_sample = 0.
         self._sample_time = self._report_time = 0.
         self._sample_count = 0
         self._report_clock = 0
-        self._callback = None
+        self._oid = self._callback = None
         self._inv_max_adc = 0.
         self._mcu_freq = 0.
         self._cmd_queue = mcu.alloc_command_queue()
@@ -355,6 +382,7 @@ class MCU_adc:
         if not self._sample_count:
             return
         self._mcu_freq = self._mcu.get_mcu_freq()
+        self._oid = self._mcu.create_oid()
         self._mcu.add_config_cmd("config_analog_in oid=%d pin=%s" % (
             self._oid, self._pin))
         last_clock, last_clock_time = self._mcu.get_last_clock()