neopixel: Rework timing so that it works on AVR micro-controllers
Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
parent
383fb1dbfd
commit
f6d1ab2ea2
|
@ -35,9 +35,6 @@ class PrinterNeoPixel:
|
||||||
self.cmd_SET_LED,
|
self.cmd_SET_LED,
|
||||||
desc=self.cmd_SET_LED_help)
|
desc=self.cmd_SET_LED_help)
|
||||||
def build_config(self):
|
def build_config(self):
|
||||||
if self.mcu.get_constant_float('CLOCK_FREQ') <= 20000000:
|
|
||||||
raise self.printer.config_error(
|
|
||||||
"Neopixel is not supported on AVR micro-controllers")
|
|
||||||
cmd_queue = self.mcu.alloc_command_queue()
|
cmd_queue = self.mcu.alloc_command_queue()
|
||||||
self.neopixel_send_cmd = self.mcu.lookup_command(
|
self.neopixel_send_cmd = self.mcu.lookup_command(
|
||||||
"neopixel_send oid=%c data=%*s", cq=cmd_queue)
|
"neopixel_send oid=%c data=%*s", cq=cmd_queue)
|
||||||
|
|
164
src/neopixel.c
164
src/neopixel.c
|
@ -11,6 +11,74 @@
|
||||||
#include "basecmd.h" // oid_alloc
|
#include "basecmd.h" // oid_alloc
|
||||||
#include "command.h" // DECL_COMMAND
|
#include "command.h" // DECL_COMMAND
|
||||||
|
|
||||||
|
// The WS2812 uses a bit-banging protocol where each bit is
|
||||||
|
// transmitted as a gpio high pulse of variable length. The various
|
||||||
|
// specs are unclear, but it is believed the timing requirements are:
|
||||||
|
// - A zero bit must have a high pulse less than 500ns.
|
||||||
|
// - A one bit must have a high pulse longer than 650ns.
|
||||||
|
// - The total bit time (gpio high to following gpio high) must not
|
||||||
|
// exceed ~5000ns. The average bit time must be at least 1250ns.
|
||||||
|
// - The specs generally indicate a minimum high pulse and low pulse
|
||||||
|
// of 200ns, but the actual requirement might be smaller.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Timing
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
|
typedef unsigned int neopixel_time_t;
|
||||||
|
|
||||||
|
static neopixel_time_t
|
||||||
|
nsecs_to_ticks(uint32_t ns)
|
||||||
|
{
|
||||||
|
return timer_from_us(ns * 1000) / 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
neopixel_check_elapsed(neopixel_time_t t1, neopixel_time_t t2, uint32_t nsecs)
|
||||||
|
{
|
||||||
|
return t2 - t1 >= nsecs_to_ticks(nsecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The AVR micro-controllers require specialized timing
|
||||||
|
#if CONFIG_MACH_AVR
|
||||||
|
|
||||||
|
#include <avr/interrupt.h> // TCNT1
|
||||||
|
|
||||||
|
static neopixel_time_t
|
||||||
|
neopixel_get_time(void)
|
||||||
|
{
|
||||||
|
return TCNT1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
neopixel_delay(neopixel_time_t start, uint32_t nsecs)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static neopixel_time_t
|
||||||
|
neopixel_get_time(void)
|
||||||
|
{
|
||||||
|
return timer_read_time();
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
neopixel_delay(neopixel_time_t start, uint32_t nsecs)
|
||||||
|
{
|
||||||
|
while (!neopixel_check_elapsed(start, neopixel_get_time(), nsecs))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/****************************************************************
|
||||||
|
* Neopixel interface
|
||||||
|
****************************************************************/
|
||||||
|
|
||||||
struct neopixel_s {
|
struct neopixel_s {
|
||||||
struct gpio_out pin;
|
struct gpio_out pin;
|
||||||
uint32_t last_req_time;
|
uint32_t last_req_time;
|
||||||
|
@ -24,25 +92,7 @@ command_config_neopixel(uint32_t *args)
|
||||||
, sizeof(*n));
|
, sizeof(*n));
|
||||||
n->pin = pin;
|
n->pin = pin;
|
||||||
}
|
}
|
||||||
#if !CONFIG_MACH_AVR
|
|
||||||
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u");
|
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u");
|
||||||
#endif
|
|
||||||
|
|
||||||
static uint32_t
|
|
||||||
timer_from_ns(uint32_t ns)
|
|
||||||
{
|
|
||||||
return timer_from_us(ns * 1000) / 1000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The WS2812 uses a bit-banging protocol where each bit is
|
|
||||||
// transmitted as a gpio high pulse of variable length. The various
|
|
||||||
// specs are unclear, but it is believed the timing requirements are:
|
|
||||||
// - A zero bit must have a high pulse less than 500ns.
|
|
||||||
// - A one bit must have a high pulse longer than 650ns.
|
|
||||||
// - The total bit time (gpio high to following gpio high) must not
|
|
||||||
// exceed ~5000ns. The average bit time must be at least 1250ns.
|
|
||||||
// - The specs generally indicate a minimum high pulse and low pulse
|
|
||||||
// of 200ns, but the actual requirement might be smaller.
|
|
||||||
|
|
||||||
static int
|
static int
|
||||||
send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
|
send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
|
||||||
|
@ -55,61 +105,51 @@ send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct gpio_out pin = n->pin;
|
struct gpio_out pin = n->pin;
|
||||||
uint32_t min_wait_time = cur, max_wait_time = cur + 0x40000000;
|
neopixel_time_t last_start = neopixel_get_time();
|
||||||
while (data_len--) {
|
while (data_len--) {
|
||||||
uint_fast8_t byte = *data++;
|
uint_fast8_t byte = *data++;
|
||||||
uint_fast8_t bits = 8;
|
uint_fast8_t bits = 8;
|
||||||
while (bits--) {
|
while (bits--) {
|
||||||
// Calculate pulse duration
|
|
||||||
uint32_t on;
|
|
||||||
if (byte & 0x80)
|
|
||||||
on = timer_from_ns(650);
|
|
||||||
else
|
|
||||||
on = timer_from_ns(200);
|
|
||||||
|
|
||||||
// Set output high
|
|
||||||
do {
|
|
||||||
irq_poll();
|
|
||||||
cur = timer_read_time();
|
|
||||||
} while (timer_is_before(cur, min_wait_time));
|
|
||||||
uint32_t off_end_time = cur;
|
|
||||||
gpio_out_write(pin, 1);
|
|
||||||
uint32_t on_start_time = timer_read_time();
|
|
||||||
min_wait_time = on_start_time + on;
|
|
||||||
|
|
||||||
// Set output low
|
|
||||||
do {
|
|
||||||
irq_poll();
|
|
||||||
cur = timer_read_time();
|
|
||||||
} while (timer_is_before(cur, min_wait_time));
|
|
||||||
uint32_t on_end_time = cur;
|
|
||||||
gpio_out_write(pin, 0);
|
|
||||||
uint32_t off_start_time = cur = timer_read_time();
|
|
||||||
min_wait_time = on_start_time + timer_from_ns(1250);
|
|
||||||
|
|
||||||
// Check for faults
|
|
||||||
if (byte & 0x80) {
|
if (byte & 0x80) {
|
||||||
// Make sure off for at least 200ns
|
// Long pulse
|
||||||
uint32_t min_off = off_start_time + timer_from_ns(200);
|
neopixel_delay(last_start, 1250);
|
||||||
if (timer_is_before(min_wait_time, min_off))
|
irq_disable();
|
||||||
min_wait_time = min_off;
|
neopixel_time_t start = neopixel_get_time();
|
||||||
} else {
|
gpio_out_toggle_noirq(pin);
|
||||||
// Make sure short on duration was no more than 500ns
|
irq_enable();
|
||||||
uint32_t max_off = off_end_time + timer_from_ns(500);
|
|
||||||
if (timer_is_before(max_off, off_start_time))
|
if (neopixel_check_elapsed(last_start, start, 4000))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
last_start = start;
|
||||||
|
byte <<= 1;
|
||||||
|
|
||||||
|
neopixel_delay(start, 650);
|
||||||
|
irq_disable();
|
||||||
|
gpio_out_toggle_noirq(pin);
|
||||||
|
irq_enable();
|
||||||
|
} else {
|
||||||
|
// Short pulse
|
||||||
|
neopixel_delay(last_start, 1250);
|
||||||
|
irq_disable();
|
||||||
|
neopixel_time_t start = neopixel_get_time();
|
||||||
|
gpio_out_toggle_noirq(pin);
|
||||||
|
neopixel_delay(start, 200);
|
||||||
|
gpio_out_toggle_noirq(pin);
|
||||||
|
irq_enable();
|
||||||
|
|
||||||
|
if (neopixel_check_elapsed(last_start, start, 4000))
|
||||||
|
goto fail;
|
||||||
|
last_start = start;
|
||||||
|
byte <<= 1;
|
||||||
}
|
}
|
||||||
byte <<= 1;
|
|
||||||
if (timer_is_before(max_wait_time, on_start_time))
|
|
||||||
goto fail;
|
|
||||||
max_wait_time = on_end_time + timer_from_us(4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n->last_req_time = cur;
|
n->last_req_time = timer_read_time();
|
||||||
return 0;
|
return 0;
|
||||||
fail:
|
fail:
|
||||||
// A hardware irq messed up the transmission - report a failure
|
// A hardware irq messed up the transmission - report a failure
|
||||||
n->last_req_time = cur;
|
gpio_out_write(pin, 0);
|
||||||
|
n->last_req_time = timer_read_time();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +168,4 @@ command_neopixel_send(uint32_t *args)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !CONFIG_MACH_AVR
|
|
||||||
DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c data=%*s");
|
DECL_COMMAND(command_neopixel_send, "neopixel_send oid=%c data=%*s");
|
||||||
#endif
|
|
||||||
|
|
Loading…
Reference in New Issue