aboutsummaryrefslogtreecommitdiffstats
path: root/src/neopixel.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/neopixel.c')
-rw-r--r--src/neopixel.c114
1 files changed, 114 insertions, 0 deletions
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 <kevin@koconnor.net>
+//
+// 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