aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2020-09-28 13:44:36 -0400
committerKevin O'Connor <kevin@koconnor.net>2020-10-29 11:59:15 -0400
commit2bb30265b513269f5e749f87a424bf6772f7fc11 (patch)
tree381f728be4b73378bb8a7869e09cf31a6ec315eb
parentbe4ad29fa343b4d20feb2fa55be9a93b84d62a51 (diff)
downloadkutter-2bb30265b513269f5e749f87a424bf6772f7fc11.tar.gz
kutter-2bb30265b513269f5e749f87a424bf6772f7fc11.tar.xz
kutter-2bb30265b513269f5e749f87a424bf6772f7fc11.zip
neopixel: Increase the maximum LED chain length
Rework neopixel updates to use an mcu buffer so that more than 18 LEDs can be in a chain. Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
-rw-r--r--klippy/extras/neopixel.py57
-rw-r--r--src/neopixel.c48
2 files changed, 78 insertions, 27 deletions
diff --git a/klippy/extras/neopixel.py b/klippy/extras/neopixel.py
index 07cad315..307651ea 100644
--- a/klippy/extras/neopixel.py
+++ b/klippy/extras/neopixel.py
@@ -1,14 +1,17 @@
# Support for "neopixel" leds
#
-# Copyright (C) 2019 Kevin O'Connor <kevin@koconnor.net>
+# Copyright (C) 2019-2020 Kevin O'Connor <kevin@koconnor.net>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
+import logging
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000
BIT_MAX_TIME=.000004
RESET_MIN_TIME=.000050
+MAX_MCU_SIZE = 500 # Sanity check on LED chain length
+
class PrinterNeoPixel:
def __init__(self, config):
self.printer = config.get_printer()
@@ -21,27 +24,33 @@ class PrinterNeoPixel:
self.pin = pin_params['pin']
self.mcu.register_config_callback(self.build_config)
self.color_order_GRB = config.getboolean("color_order_GRB", True)
- self.chain_count = config.getint('chain_count', 1, minval=1, maxval=18)
- self.neopixel_send_cmd = None
+ self.chain_count = config.getint('chain_count', 1,
+ minval=1, maxval=MAX_MCU_SIZE//3)
+ self.neopixel_update_cmd = self.neopixel_send_cmd = None
# Initial color
+ self.color_data = bytearray(self.chain_count * 3)
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.)
self.update_color_data(red, green, blue)
- self.printer.register_event_handler("klippy:connect", self.send_data)
+ self.old_color_data = bytearray([d ^ 1 for d in self.color_data])
# Register commands
+ self.printer.register_event_handler("klippy:connect", self.send_data)
gcode = self.printer.lookup_object('gcode')
gcode.register_mux_command("SET_LED", "LED", name, self.cmd_SET_LED,
desc=self.cmd_SET_LED_help)
def build_config(self):
bmt = self.mcu.seconds_to_clock(BIT_MAX_TIME)
rmt = self.mcu.seconds_to_clock(RESET_MIN_TIME)
- self.mcu.add_config_cmd("config_neopixel oid=%d pin=%s"
+ self.mcu.add_config_cmd("config_neopixel oid=%d pin=%s data_size=%d"
" bit_max_ticks=%d reset_min_ticks=%d"
- % (self.oid, self.pin, bmt, rmt))
+ % (self.oid, self.pin, self.chain_count * 3,
+ bmt, rmt))
cmd_queue = self.mcu.alloc_command_queue()
- self.neopixel_send_cmd = self.mcu.lookup_command(
- "neopixel_send oid=%c data=%*s", cq=cmd_queue)
+ self.neopixel_update_cmd = self.mcu.lookup_command(
+ "neopixel_update oid=%c pos=%hu data=%*s", cq=cmd_queue)
+ self.neopixel_send_cmd = self.mcu.lookup_query_command(
+ "neopixel_send oid=%c", "neopixel_result success=%c", cq=cmd_queue)
def update_color_data(self, red, green, blue, index=None):
red = int(red * 255. + .5)
blue = int(blue * 255. + .5)
@@ -51,13 +60,37 @@ class PrinterNeoPixel:
else:
color_data = [red, green, blue]
if index is None:
- self.color_data = color_data * self.chain_count
+ self.color_data[:] = color_data * self.chain_count
else:
self.color_data[(index-1)*3:index*3] = color_data
def send_data(self, minclock=0):
- self.neopixel_send_cmd.send([self.oid, self.color_data],
- minclock=minclock,
- reqclock=BACKGROUND_PRIORITY_CLOCK)
+ old_data, new_data = self.old_color_data, self.color_data
+ if new_data == old_data:
+ return
+ # Find the position of all changed bytes in this framebuffer
+ diffs = [[i, 1] for i, (n, o) in enumerate(zip(new_data, old_data))
+ if n != o]
+ # Batch together changes that are close to each other
+ for i in range(len(diffs)-2, -1, -1):
+ pos, count = diffs[i]
+ nextpos, nextcount = diffs[i+1]
+ if pos + 5 >= nextpos and nextcount < 16:
+ diffs[i][1] = nextcount + (nextpos - pos)
+ del diffs[i+1]
+ # Transmit changes
+ ucmd = self.neopixel_update_cmd.send
+ for pos, count in diffs:
+ ucmd([self.oid, pos, new_data[pos:pos+count]],
+ reqclock=BACKGROUND_PRIORITY_CLOCK)
+ old_data[:] = new_data
+ # Instruct mcu to update the LEDs
+ scmd = self.neopixel_send_cmd.send
+ for i in range(8):
+ params = scmd([self.oid], minclock=minclock)
+ if params['success']:
+ break
+ else:
+ logging.info("Neopixel update did not succeed")
cmd_SET_LED_help = "Set the color of an LED"
def cmd_SET_LED(self, gcmd):
# Parse parameters
diff --git a/src/neopixel.c b/src/neopixel.c
index d5aab685..5bf17cad 100644
--- a/src/neopixel.c
+++ b/src/neopixel.c
@@ -4,12 +4,14 @@
//
// This file may be distributed under the terms of the GNU GPLv3 license.
+#include <string.h> // memcpy
#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
+#include "sched.h" // sched_shutdown
// The WS2812 uses a bit-banging protocol where each bit is
// transmitted as a gpio high pulse of variable length. The various
@@ -85,23 +87,29 @@ struct neopixel_s {
struct gpio_out pin;
neopixel_time_t bit_max_ticks;
uint32_t last_req_time, reset_min_ticks;
+ uint16_t data_size;
+ uint8_t data[0];
};
void
command_config_neopixel(uint32_t *args)
{
struct gpio_out pin = gpio_out_setup(args[1], 0);
+ uint16_t data_size = args[2];
+ if (data_size & 0x8000)
+ shutdown("Invalid neopixel data_size");
struct neopixel_s *n = oid_alloc(args[0], command_config_neopixel
- , sizeof(*n));
+ , sizeof(*n) + data_size);
n->pin = pin;
- n->bit_max_ticks = args[2];
- n->reset_min_ticks = args[3];
+ n->data_size = data_size;
+ n->bit_max_ticks = args[3];
+ n->reset_min_ticks = args[4];
}
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u"
- " bit_max_ticks=%u reset_min_ticks=%u");
+ " data_size=%hu bit_max_ticks=%u reset_min_ticks=%u");
static int
-send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
+send_data(struct neopixel_s *n)
{
// Make sure the reset time has elapsed since last request
uint32_t last_req_time = n->last_req_time, rmt = n->reset_min_ticks;
@@ -112,6 +120,8 @@ send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
}
// Transmit data
+ uint8_t *data = n->data;
+ uint_fast16_t data_len = n->data_size;
struct gpio_out pin = n->pin;
neopixel_time_t last_start = neopixel_get_time();
neopixel_time_t bit_max_ticks = n->bit_max_ticks;
@@ -165,18 +175,26 @@ fail:
}
void
-command_neopixel_send(uint32_t *args)
+command_neopixel_update(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_fast16_t pos = args[1];
+ uint_fast8_t data_len = args[2];
+ uint8_t *data = (void*)(size_t)args[3];
+ if (pos & 0x8000 || pos + data_len > n->data_size)
+ shutdown("Invalid neopixel update command");
+ memcpy(&n->data[pos], data, data_len);
+}
+DECL_COMMAND(command_neopixel_update,
+ "neopixel_update oid=%c pos=%hu data=%*s");
- uint_fast8_t retry = 8;
- while (retry--) {
- int ret = send_data(n, data, data_len);
- if (!ret)
- break;
- }
+void
+command_neopixel_send(uint32_t *args)
+{
+ uint8_t oid = args[0];
+ struct neopixel_s *n = oid_lookup(oid, command_config_neopixel);
+ int ret = send_data(n);
+ sendf("neopixel_result success=%c", ret ? 0 : 1);
}
-DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c data=%*s");
+DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c");