-
-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WS2801: Support for WS2801 LEDs using SW SPI and HW SPI
Since none of the existing LED drivers worked properly with WS2801 LED modules this driver was added. Tested on Arduino Uno Will not pass all regression tests due to dict files being outdated on gh. Signed-off-by: Anders Törnberg <[email protected]>
- Loading branch information
Showing
4 changed files
with
240 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# Support for "ws2801" leds | ||
# coding=utf-8 | ||
# Copyright (C) 2024 Anders Törnberg <[email protected]> | ||
# | ||
# This file may be distributed under the terms of the GNU GPLv3 license. | ||
import logging | ||
from . import bus | ||
|
||
BACKGROUND_PRIORITY_CLOCK = 0x7fffffff00000000 | ||
|
||
MAX_MCU_SIZE = 500 # Sanity check on LED chain length | ||
|
||
class WS2801: | ||
def __init__(self, config): | ||
self.printer = printer = config.get_printer() | ||
self.mutex = printer.get_reactor().mutex() | ||
# Configure ws2801 | ||
self.spi = bus.MCU_SPI_from_config(config, 0, default_speed=1000000) | ||
self.mcu = self.spi.get_mcu() | ||
self.oid = self.mcu.create_oid() | ||
self.mcu.register_config_callback(self.build_config) | ||
self.ws8201_update_cmd = self.ws8201_send_cmd = None | ||
# Build color map | ||
chain_count = config.getint('chain_count', 1, minval=1) | ||
color_order = config.getlist("color_order", ["RGB"]) | ||
if len(color_order) == 1: | ||
color_order = [color_order[0]] * chain_count | ||
if len(color_order) != chain_count: | ||
raise config.error("color_order does not match chain_count") | ||
color_indexes = [] | ||
for lidx, co in enumerate(color_order): | ||
if sorted(co) not in (sorted("RGB"), sorted("RGB")): | ||
raise config.error("Invalid color_order '%s'" % (co,)) | ||
color_indexes.extend([(lidx, "RGB".index(c)) for c in co]) | ||
self.color_map = list(enumerate(color_indexes)) | ||
if len(self.color_map) > MAX_MCU_SIZE: | ||
raise config.error("WS2801 chain too long") | ||
# Initialize color data | ||
pled = printer.load_object(config, "led") | ||
self.led_helper = pled.setup_helper(config, self.update_leds, | ||
chain_count) | ||
self.color_data = bytearray(len(self.color_map)) | ||
self.update_color_data(self.led_helper.get_status()['color_data']) | ||
self.old_color_data = bytearray([d ^ 1 for d in self.color_data]) | ||
# Register callbacks | ||
printer.register_event_handler("klippy:connect", self.send_data) | ||
def build_config(self): | ||
print(self.spi.get_oid()) | ||
self.mcu.add_config_cmd("config_ws2801 oid=%d spi_oid=%d data_size=%d" | ||
% (self.oid, self.spi.get_oid(), len(self.color_data))) | ||
cmd_queue = self.mcu.alloc_command_queue() | ||
self.ws2801_update_cmd = self.mcu.lookup_command( | ||
"ws2801_update oid=%c pos=%hu data=%*s", cq=cmd_queue) | ||
self.ws2801_send_cmd = self.mcu.lookup_query_command( | ||
"ws2801_send oid=%c", "ws2801_result oid=%c success=%c", | ||
oid=self.oid, cq=cmd_queue) | ||
def update_color_data(self, led_state): | ||
color_data = self.color_data | ||
for cdidx, (lidx, cidx) in self.color_map: | ||
color_data[cdidx] = int(led_state[lidx][cidx] * 255. + .5) | ||
def send_data(self, print_time=None): | ||
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 | ||
for pos, count in diffs: | ||
print(new_data[pos:pos+count]) | ||
ucmd = self.ws2801_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 | ||
minclock = 0 | ||
if print_time is not None: | ||
minclock = self.mcu.print_time_to_clock(print_time) | ||
scmd = self.ws2801_send_cmd.send | ||
if self.printer.get_start_args().get('debugoutput') is not None: | ||
return | ||
for i in range(8): | ||
params = scmd([self.oid], minclock=minclock, | ||
reqclock=BACKGROUND_PRIORITY_CLOCK) | ||
if params['success']: | ||
break | ||
else: | ||
logging.info("WS2801 update did not succeed") | ||
def update_leds(self, led_state, print_time): | ||
def reactor_bgfunc(eventtime): | ||
with self.mutex: | ||
self.update_color_data(led_state) | ||
self.send_data(print_time) | ||
self.printer.get_reactor().register_callback(reactor_bgfunc) | ||
def get_status(self, eventtime=None): | ||
return self.led_helper.get_status(eventtime) | ||
|
||
def load_config_prefix(config): | ||
return WS2801(config) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Support for SW and HW SPI commands to WS2812 type LEDs | ||
// | ||
// Copyright (C) 2024 Anders Törnberg <[email protected]> | ||
// | ||
// 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 | ||
#include "spicmds.h" // spidev_se | ||
|
||
/**************************************************************** | ||
* Timing | ||
****************************************************************/ | ||
|
||
typedef unsigned int ws2801_time_t; | ||
|
||
static ws2801_time_t | ||
nsecs_to_ticks(uint32_t ns) | ||
{ | ||
return timer_from_us(ns * 1000) / 1000000; | ||
} | ||
|
||
#define MIN_TICKS_BETWEEN_REQUESTS nsecs_to_ticks(500000) | ||
|
||
/**************************************************************** | ||
* WS2801 interface | ||
****************************************************************/ | ||
|
||
struct ws2801_s { | ||
struct spidev_s *spi; | ||
uint32_t last_req_time; | ||
uint16_t data_size; | ||
uint8_t data[0]; | ||
}; | ||
|
||
void | ||
command_config_ws2801(uint32_t *args) | ||
{ | ||
uint16_t data_size = args[2]; | ||
if (data_size & 0x8000) | ||
shutdown("Invalid ws2801 data_size"); | ||
struct ws2801_s *ws = oid_alloc(args[0], command_config_ws2801 | ||
, sizeof(*ws) + data_size); | ||
ws->spi = spidev_oid_lookup(args[1]); | ||
ws->data_size = data_size; | ||
} | ||
DECL_COMMAND(command_config_ws2801, "config_ws2801 oid=%c spi_oid=%c" | ||
" data_size=%hu"); | ||
|
||
static int | ||
send_data(struct ws2801_s *ws) | ||
{ | ||
// Make sure the reset time has elapsed since last request | ||
uint32_t last_req_time = ws->last_req_time, \ | ||
rmt = MIN_TICKS_BETWEEN_REQUESTS; | ||
uint32_t cur = timer_read_time(); | ||
while (cur - last_req_time < rmt) { | ||
irq_poll(); | ||
cur = timer_read_time(); | ||
} | ||
|
||
// Transmit data | ||
uint8_t *data = ws->data; | ||
uint_fast16_t data_len = ws->data_size; | ||
spidev_transfer(ws->spi, 0, data_len, data); | ||
return 0; | ||
} | ||
|
||
void | ||
command_ws2801_update(uint32_t *args) | ||
{ | ||
uint8_t oid = args[0]; | ||
struct ws2801_s *ws = oid_lookup(oid, command_config_ws2801); | ||
uint_fast16_t pos = args[1]; | ||
uint_fast8_t data_len = args[2]; | ||
uint8_t *data = command_decode_ptr(args[3]); | ||
if (pos & 0x8000 || pos + data_len > ws->data_size) | ||
shutdown("Invalid ws2801 update command"); | ||
memcpy(&ws->data[pos], data, data_len); | ||
} | ||
DECL_COMMAND(command_ws2801_update, | ||
"ws2801_update oid=%c pos=%hu data=%*s"); | ||
|
||
void | ||
command_ws2801_send(uint32_t *args) | ||
{ | ||
uint8_t oid = args[0]; | ||
struct ws2801_s *ws = oid_lookup(oid, command_config_ws2801); | ||
int ret = send_data(ws); | ||
sendf("ws2801_result oid=%c success=%c", oid, ret ? 0 : 1); | ||
} | ||
DECL_COMMAND(command_ws2801_send, "ws2801_send oid=%c"); |