diff --git a/config/example-extras.cfg b/config/example-extras.cfg index 46c0d6ca..27ad4ec5 100644 --- a/config/example-extras.cfg +++ b/config/example-extras.cfg @@ -1648,6 +1648,16 @@ #measurement_delay: 100 +# Neopixel (aka WS2812) LED support (one may define any number of +# sections with a "neopixel" prefix). One may set the LED color via a +# "SET_NEOPIXEL NEOPIXEL=my_neopixel RED=0.1 GREEN=0.1 BLUE=0.1" type +# extended g-code commands. +#[neopixel my_neopixel] +#pin: +# The pin connected to the neopixel. This parameter must be +# provided. + + # Firmware filament retraction. This enables G10 (retract) and G11 # (unretract) GCODE commands issued by many slicers. The parameters # below provide startup defaults, although the values can be adjusted diff --git a/docs/G-Codes.md b/docs/G-Codes.md index de8c005d..78be3e72 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -191,6 +191,14 @@ The following command is available when an "output_pin" config section is enabled: - `SET_PIN PIN=config_name VALUE=` +## Neopixel Commands + +The following command is available when a "neopixel" config section +is enabled: +- `SET_NEOPIXEL NEOPIXEL= RED= GREEN= + BLUE=`: This sets the neopixel LED output. Each must + be between 0.0 and 1.0. + ## Servo Commands The following commands are available when a "servo" config section is diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py new file mode 100644 index 00000000..6988b645 --- /dev/null +++ b/klippy/extras/neopixel.py @@ -0,0 +1,45 @@ +# Support for "neopixel" leds +# +# Copyright (C) 2019 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +class PrinterNeoPixel: + def __init__(self, config): + self.printer = config.get_printer() + name = config.get_name().split()[1] + # Configure neopixel + ppins = self.printer.lookup_object('pins') + pin_params = ppins.lookup_pin(config.get('pin')) + self.mcu = pin_params['chip'] + self.oid = self.mcu.create_oid() + self.mcu.add_config_cmd("config_neopixel oid=%d pin=%s" + % (self.oid, pin_params['pin'])) + self.mcu.register_config_callback(self.build_config) + self.neopixel_send_cmd = None + # Register commands + self.gcode = self.printer.lookup_object('gcode') + self.gcode.register_mux_command("SET_NEOPIXEL", "NEOPIXEL", name, + self.cmd_SET_NEOPIXEL, + desc=self.cmd_SET_NEOPIXEL_help) + def build_config(self): + cmd_queue = self.mcu.alloc_command_queue() + self.neopixel_send_cmd = self.mcu.lookup_command( + "neopixel_send oid=%c data=%*s", cq=cmd_queue) + cmd_SET_NEOPIXEL_help = "Set the color of a neopixel led" + def cmd_SET_NEOPIXEL(self, params): + # Parse parameters + red = self.gcode.get_float('RED', params, 0., minval=0., maxval=1.) + green = self.gcode.get_float('GREEN', params, 0., minval=0., maxval=1.) + blue = self.gcode.get_float('BLUE', params, 0., minval=0., maxval=1.) + red = int(red * 255. + .5) + blue = int(blue * 255. + .5) + green = int(green * 255. + .5) + # Send command + print_time = self.printer.lookup_object('toolhead').get_last_move_time() + minclock = self.mcu.print_time_to_clock(print_time) + self.neopixel_send_cmd.send([self.oid, [green, red, blue]], + minclock=minclock) + +def load_config_prefix(config): + return PrinterNeoPixel(config) diff --git a/src/Makefile b/src/Makefile index c838e911..2eff8a93 100644 --- a/src/Makefile +++ b/src/Makefile @@ -7,4 +7,4 @@ src-$(CONFIG_HAVE_GPIO_SPI) += spicmds.c thermocouple.c src-$(CONFIG_HAVE_GPIO_I2C) += i2ccmds.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += pwmcmds.c src-$(CONFIG_HAVE_GPIO_BITBANGING) += lcd_st7920.c lcd_hd44780.c buttons.c \ - tmcuart.c spi_software.c + tmcuart.c spi_software.c neopixel.c diff --git a/src/neopixel.c b/src/neopixel.c new file mode 100644 index 00000000..41ca98dc --- /dev/null +++ b/src/neopixel.c @@ -0,0 +1,114 @@ +// Support for bit-banging commands to WS2812 type "neopixel" LEDs +// +// Copyright (C) 2019 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "autoconf.h" // CONFIG_MACH_AVR +#include "board/gpio.h" // gpio_out_write +#include "board/irq.h" // irq_poll +#include "board/misc.h" // timer_read_time +#include "basecmd.h" // oid_alloc +#include "command.h" // DECL_COMMAND + +struct neopixel_s { + struct gpio_out pin; + uint32_t last_req_time; +}; + +void +command_config_neopixel(uint32_t *args) +{ + struct gpio_out pin = gpio_out_setup(args[1], 0); + struct neopixel_s *n = oid_alloc(args[0], command_config_neopixel + , sizeof(*n)); + n->pin = pin; +} +#if !CONFIG_MACH_AVR +DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u"); +#endif + +uint32_t +timer_from_ns(uint32_t ns) +{ + return timer_from_us(ns * 1000) / 1000000; +} + +static int +send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len) +{ + // Make sure at least 50us has passed since last request + uint32_t last_req_time = n->last_req_time, cur = timer_read_time(); + while (cur - last_req_time < timer_from_us(50)) { + irq_poll(); + cur = timer_read_time(); + } + + struct gpio_out pin = n->pin; + uint32_t expire_time = cur + 0x40000000; + while (data_len--) { + uint_fast8_t byte = *data++; + uint_fast8_t bits = 8; + while (bits--) { + // Calculate pulse duration + uint32_t on, off; + if (byte & 0x80) { + on = timer_from_ns(700 - 150); + off = timer_from_ns(600 - 150); + } else { + on = timer_from_ns(350 - 150); + off = timer_from_ns(800 - 150); + } + byte <<= 1; + + // Set output high + gpio_out_toggle(pin); + uint32_t on_start_time = timer_read_time(); + if (timer_is_before(expire_time, on_start_time)) + goto fail; + cur = on_start_time; + while (timer_is_before(cur, on_start_time + on)) { + irq_poll(); + cur = timer_read_time(); + } + + // Set output low + gpio_out_toggle(pin); + uint32_t off_start_time = timer_read_time(); + expire_time = on_start_time + on + timer_from_ns(300); + if (timer_is_before(expire_time, off_start_time)) + goto fail; + cur = off_start_time; + while (timer_is_before(cur, off_start_time + off)) { + irq_poll(); + cur = timer_read_time(); + } + expire_time = off_start_time + off + timer_from_ns(300); + } + } + n->last_req_time = cur; + return 0; +fail: + // A hardware irq messed up the transmission - report a failure + n->last_req_time = cur; + return -1; +} + +void +command_neopixel_send(uint32_t *args) +{ + uint8_t oid = args[0]; + struct neopixel_s *n = oid_lookup(oid, command_config_neopixel); + uint_fast8_t data_len = args[1]; + uint8_t *data = (void*)(size_t)args[2]; + + uint_fast8_t retry = 8; + while (retry--) { + int ret = send_data(n, data, data_len); + if (!ret) + break; + } +} +#if !CONFIG_MACH_AVR +DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c data=%*s"); +#endif