led: Add support for PWM controlled LEDs

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2022-03-23 12:04:42 -04:00
parent 3340bb2ffd
commit 96795def9c
3 changed files with 258 additions and 112 deletions

View File

@ -2505,7 +2505,125 @@ with the SET_FAN_SPEED [gcode command](G-Codes.md#fan_generic).
# See the "fan" section for a description of the above parameters.
```
## Additional servos, LEDs, buttons, and other pins
## LEDs
### [led]
Support for LEDs (and LED strips) controlled via micro-controller PWM
pins (one may define any number of sections with an "led" prefix). See
the [command reference](G-Codes.md#led) for more information.
```
[led my_led]
#red_pin:
#green_pin:
#blue_pin:
#white_pin:
# The pin controlling the given LED color. At least one of the above
# parameters must be provided.
#cycle_time: 0.010
# 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.010 seconds.
#hardware_pwm: False
# Enable this to use hardware PWM instead of software PWM. When
# using hardware PWM the actual cycle time is constrained by the
# implementation and may be significantly different than the
# requested cycle_time. The default is False.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
#initial_WHITE: 0.0
# Sets the initial LED color. Each value should be between 0.0 and
# 1.0. The default for each color is 0.
```
### [neopixel]
Neopixel (aka WS2812) LED support (one may define any number of
sections with a "neopixel" prefix). See the
[command reference](G-Codes.md#led) for more information.
```
[neopixel my_neopixel]
pin:
# The pin connected to the neopixel. This parameter must be
# provided.
#chain_count:
# The number of Neopixel chips that are "daisy chained" to the
# provided pin. The default is 1 (which indicates only a single
# Neopixel is connected to the pin).
#color_order: GRB
# Set the pixel order required by the LED hardware. Options are GRB,
# RGB, BRG, BGR, GRBW, or RGBW. The default is GRB.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
#initial_WHITE: 0.0
# See the "led" section for information on these parameters.
```
### [dotstar]
Dotstar (aka APA102) LED support (one may define any number of
sections with a "dotstar" prefix). See the
[command reference](G-Codes.md#led) for more information.
```
[dotstar my_dotstar]
data_pin:
# The pin connected to the data line of the dotstar. This parameter
# must be provided.
clock_pin:
# The pin connected to the clock line of the dotstar. This parameter
# must be provided.
#chain_count:
# See the "neopixel" section for information on this parameter.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
# See the "led" section for information on these parameters.
```
### [pca9533]
PCA9533 LED support. The PCA9533 is used on the mightyboard.
```
[pca9533 my_pca9533]
#i2c_address: 98
# The i2c address that the chip is using on the i2c bus. Use 98 for
# the PCA9533/1, 99 for the PCA9533/2. The default is 98.
#i2c_mcu:
#i2c_bus:
#i2c_speed:
# See the "common I2C settings" section for a description of the
# above parameters.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
#initial_WHITE: 0.0
# See the "led" section for information on these parameters.
```
### [pca9632]
PCA9632 LED support. The PCA9632 is used on the FlashForge Dreamer.
```
[pca9632 my_pca9632]
scl_pin:
# The SCL "clock" pin. This parameter must be provided.
sda_pin:
# The SDA "data" pin. This parameter must be provided.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
#initial_WHITE: 0.0
# See the "led" section for information on these parameters.
```
## Additional servos, buttons, and other pins
### [servo]
@ -2538,100 +2656,6 @@ pin:
# send any signal at startup.
```
### [neopixel]
Neopixel (aka WS2812) LED support (one may define any number of
sections with a "neopixel" prefix). One may set the LED color via
"SET_LED LED=my_neopixel RED=0.1 GREEN=0.1 BLUE=0.1" type extended
[g-code commands](G-Codes.md#neopixel).
```
[neopixel my_neopixel]
pin:
# The pin connected to the neopixel. This parameter must be
# provided.
#chain_count:
# The number of Neopixel chips that are "daisy chained" to the
# provided pin. The default is 1 (which indicates only a single
# Neopixel is connected to the pin).
#color_order: GRB
# Set the pixel order required by the LED hardware. Options are GRB,
# RGB, BRG, BGR, GRBW, or RGBW. The default is GRB.
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
#initial_WHITE: 0.0
# Sets the initial LED color of the Neopixel. Each value should be
# between 0.0 and 1.0. The WHITE option is only available on RGBW
# LEDs. The default for each color is 0.
```
### [dotstar]
Dotstar (aka APA102) LED support (one may define any number of
sections with a "dotstar" prefix). One may set the LED color via
"SET_LED LED=my_dotstar RED=0.1 GREEN=0.1 BLUE=0.1" type extended
[g-code commands](G-Codes.md#neopixel).
```
[dotstar my_dotstar]
data_pin:
# The pin connected to the data line of the dotstar. This parameter
# must be provided.
clock_pin:
# The pin connected to the clock line of the dotstar. This parameter
# must be provided.
#chain_count:
#initial_RED: 0.0
#initial_GREEN: 0.0
#initial_BLUE: 0.0
# See the "neopixel" section for information on these parameters.
```
### [PCA9533]
PCA9533 LED support. The PCA9533 is used on the mightyboard.
```
[pca9533 my_pca9533]
#i2c_address: 98
# The i2c address that the chip is using on the i2c bus. Use 98 for
# the PCA9533/1, 99 for the PCA9533/2. The default is 98.
#i2c_mcu:
#i2c_bus:
#i2c_speed:
# See the "common I2C settings" section for a description of the
# above parameters.
#initial_RED: 0
#initial_GREEN: 0
#initial_BLUE: 0
#initial_WHITE: 0
# The PCA9533 only supports 1 or 0. The default is 0. On the
# mightyboard, the white led is not populated.
# Use GCODE to modify led values after startup.
# set_led led=my_pca9533 red=1 green=1 blue=1
```
### [PCA9632]
PCA9632 LED support. The PCA9632 is used on the FlashForge Dreamer.
```
[pca9632 my_pca9632]
scl_pin:
# The SCL "clock" pin. This parameter must be provided.
sda_pin:
# The SDA "data" pin. This parameter must be provided.
#initial_RED: 0
#initial_GREEN: 0
#initial_BLUE: 0
#initial_WHITE: 0
# PCA9632 supports individual LED PWM.
# Values range from 0.0 to 1.0. The default is 0.0.
# On the FlashForge Dreamer, the white led is not populated.
# Use GCODE to modify led values after startup.
# set_led led=my_pca9632 red=1.0 green=1.0 blue=1.0 white=0.0
```
### [gcode_button]
Execute gcode when a button is pressed or released (or when a pin

View File

@ -690,29 +690,29 @@ scheduled to run after the stepper move completes, however if a manual
stepper move uses SYNC=0 then future G-Code movement commands may run
in parallel with the stepper movement.
### [neopixel]
### [led]
The following command is available when a
[neopixel config section](Config_Reference.md#neopixel) or
[dotstar config section](Config_Reference.md#dotstar) is enabled.
The following command is available when any of the
[led config sections](Config_Reference.md#leds) are enabled.
#### SET_LED
`SET_LED LED=<config_name> RED=<value> GREEN=<value> BLUE=<value>
WHITE=<value> [INDEX=<index>] [TRANSMIT=0] [SYNC=1]`: This sets the
LED output. Each color `<value>` must be between 0.0 and 1.0. The
WHITE option is only valid on RGBW LEDs. If multiple LED chips are
daisy-chained then one may specify INDEX to alter the color of just
the given chip (1 for the first chip, 2 for the second, etc.). If
INDEX is not provided then all LEDs in the daisy-chain will be set to
the provided color. If TRANSMIT=0 is specified then the color change
will only be made on the next SET_LED command that does not specify
TRANSMIT=0; this may be useful in combination with the INDEX parameter
to batch multiple updates in a daisy-chain. By default, the SET_LED
command will sync it's changes with other ongoing gcode commands.
This can lead to undesirable behavior if LEDs are being set while the
printer is not printing as it will reset the idle timeout. If careful
timing is not needed, the optional SYNC=0 parameter can be specified
to apply the changes instantly and not reset the idle timeout.
WHITE option is only valid on RGBW LEDs. If the LED supports multiple
chips in a daisy-chain then one may specify INDEX to alter the color
of just the given chip (1 for the first chip, 2 for the second,
etc.). If INDEX is not provided then all LEDs in the daisy-chain will
be set to the provided color. If TRANSMIT=0 is specified then the
color change will only be made on the next SET_LED command that does
not specify TRANSMIT=0; this may be useful in combination with the
INDEX parameter to batch multiple updates in a daisy-chain. By
default, the SET_LED command will sync it's changes with other ongoing
gcode commands. This can lead to undesirable behavior if LEDs are
being set while the printer is not printing as it will reset the idle
timeout. If careful timing is not needed, the optional SYNC=0
parameter can be specified to apply the changes without resetting the
idle timeout.
### [output_pin]

122
klippy/extras/led.py Normal file
View File

@ -0,0 +1,122 @@
# Support for PWM driven LEDs
#
# Copyright (C) 2019-2022 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
import logging
# Helper code for common LED initialization and control
class LEDHelper:
def __init__(self, config, update_func, led_count=1, has_white=False):
self.printer = config.get_printer()
self.update_func = update_func
self.led_count = led_count
# Initial color
red = config.getfloat('initial_RED', 0., minval=0., maxval=1.)
green = config.getfloat('initial_GREEN', 0., minval=0., maxval=1.)
blue = config.getfloat('initial_BLUE', 0., minval=0., maxval=1.)
white = 0.
if has_white:
white = config.getfloat('initial_WHITE', 0., minval=0., maxval=1.)
self.led_state = [(red, green, blue, white)] * led_count
# Register commands
name = config.get_name().split()[-1]
gcode = self.printer.lookup_object('gcode')
gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED,
desc=self.cmd_SET_LED_help)
cmd_SET_LED_help = "Set the color of an LED"
def cmd_SET_LED(self, gcmd):
# Parse parameters
red = gcmd.get_float('RED', 0., minval=0., maxval=1.)
green = gcmd.get_float('GREEN', 0., minval=0., maxval=1.)
blue = gcmd.get_float('BLUE', 0., minval=0., maxval=1.)
white = gcmd.get_float('WHITE', 0., minval=0., maxval=1.)
index = gcmd.get_int('INDEX', None, minval=1, maxval=self.led_count)
transmit = gcmd.get_int('TRANSMIT', 1)
sync = gcmd.get_int('SYNC', 1)
color = (red, green, blue, white)
# Update and transmit data
def lookahead_bgfunc(print_time):
if index is None:
new_led_state = [color] * self.led_count
if self.led_state == new_led_state:
return
self.led_state = new_led_state
else:
if self.led_state[index - 1] == color:
return
self.led_state = led_state = list(self.led_state)
led_state[index - 1] = color
if transmit:
try:
self.update_func(self.led_state, print_time)
except self.printer.command_error as e:
logging.exception("led update transmit error")
if sync:
#Sync LED Update with print time and send
toolhead = self.printer.lookup_object('toolhead')
toolhead.register_lookahead_callback(lookahead_bgfunc)
else:
#Send update now (so as not to wake toolhead and reset idle_timeout)
lookahead_bgfunc(None)
def get_status(self, eventtime=None):
return {'color_data': self.led_state}
# Main LED tracking code
class PrinterLED:
def __init__(self, config):
self.printer = config.get_printer()
def setup_helper(self, config, update_func, led_count=1, has_white=False):
return LEDHelper(config, update_func, led_count, has_white)
PIN_MIN_TIME = 0.100
MAX_SCHEDULE_TIME = 5.0
# Handler for PWM controlled LEDs
class PrinterPWMLED:
def __init__(self, config):
self.printer = printer = config.get_printer()
# Configure pwm pins
ppins = printer.lookup_object('pins')
cycle_time = config.getfloat('cycle_time', 0.010, above=0.,
maxval=MAX_SCHEDULE_TIME)
hardware_pwm = config.getboolean('hardware_pwm', False)
self.pins = []
for i, name in enumerate(("red", "green", "blue", "white")):
pin_name = config.get(name + '_pin', None)
if pin_name is None:
continue
mcu_pin = ppins.setup_pin('pwm', pin_name)
mcu_pin.setup_max_duration(0.)
mcu_pin.setup_cycle_time(cycle_time, hardware_pwm)
self.pins.append((i, mcu_pin))
if not self.pins:
raise config.error("No LED pin definitions found in '%s'"
% (config.get_name(),))
self.last_print_time = 0.
# Initialize color data
pled = printer.load_object(config, "led")
self.led_helper = pled.setup_helper(config, self.update_leds, 1, True)
self.prev_color = color = self.led_helper.get_status()['color_data'][0]
for idx, mcu_pin in self.pins:
mcu_pin.setup_start_value(color[idx], 0.)
def update_leds(self, led_state, print_time):
if print_time is None:
eventtime = self.printer.get_reactor().monotonic()
mcu = self.pins[0][1].get_mcu()
print_time = mcu.estimated_print_time(eventtime) + PIN_MIN_TIME
print_time = max(print_time, self.last_print_time + PIN_MIN_TIME)
color = led_state[0]
for idx, mcu_pin in self.pins:
if self.prev_color[idx] != color[idx]:
mcu_pin.set_pwm(print_time, color[idx])
self.last_print_time = print_time
self.prev_color = color
def get_status(self, eventtime=None):
return self.led_helper.get_status(eventtime)
def load_config(config):
return PrinterLED(config)
def load_config_prefix(config):
return PrinterPWMLED(config)