neopixel: Update to more flexible bit-banging timing

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
This commit is contained in:
Kevin O'Connor 2019-07-19 13:53:34 -04:00
parent 46041f5411
commit 4e5ddff00b
1 changed files with 38 additions and 20 deletions

View File

@ -28,12 +28,22 @@ command_config_neopixel(uint32_t *args)
DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u"); DECL_COMMAND(command_config_neopixel, "config_neopixel oid=%c pin=%u");
#endif #endif
uint32_t static uint32_t
timer_from_ns(uint32_t ns) timer_from_ns(uint32_t ns)
{ {
return timer_from_us(ns * 1000) / 1000000; 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)
{ {
@ -51,47 +61,55 @@ send_data(struct neopixel_s *n, uint8_t *data, uint_fast8_t data_len)
uint_fast8_t bits = 8; uint_fast8_t bits = 8;
while (bits--) { while (bits--) {
// Calculate pulse duration // Calculate pulse duration
uint32_t on, off; uint32_t on;
if (byte & 0x80) { if (byte & 0x80)
on = timer_from_ns(700 - 150); on = timer_from_ns(650);
off = timer_from_ns(600 - 150); else
} else { on = timer_from_ns(200);
on = timer_from_ns(350 - 150);
off = timer_from_ns(800 - 150);
}
byte <<= 1;
// Set output high // Set output high
do { do {
irq_poll(); irq_poll();
cur = timer_read_time(); cur = timer_read_time();
} while (timer_is_before(cur, min_wait_time)); } while (timer_is_before(cur, min_wait_time));
uint32_t off_end_time = cur;
gpio_out_write(pin, 1); gpio_out_write(pin, 1);
uint32_t on_start_time = timer_read_time(); uint32_t on_start_time = timer_read_time();
if (timer_is_before(max_wait_time, on_start_time))
goto fail;
min_wait_time = on_start_time + on; min_wait_time = on_start_time + on;
max_wait_time = cur + on + timer_from_ns(300);
// Set output low // Set output low
do { do {
irq_poll(); irq_poll();
cur = timer_read_time(); cur = timer_read_time();
} while (timer_is_before(cur, min_wait_time)); } while (timer_is_before(cur, min_wait_time));
uint32_t on_end_time = cur;
gpio_out_write(pin, 0); gpio_out_write(pin, 0);
uint32_t off_start_time = timer_read_time(); uint32_t off_start_time = cur = timer_read_time();
if (timer_is_before(max_wait_time, off_start_time)) min_wait_time = on_start_time + timer_from_ns(1250);
// Check for faults
if (byte & 0x80) {
// Make sure off for at least 200ns
uint32_t min_off = off_start_time + timer_from_ns(200);
if (timer_is_before(min_wait_time, min_off))
min_wait_time = min_off;
} else {
// Make sure short on duration was no more than 500ns
uint32_t max_off = off_end_time + timer_from_ns(500);
if (timer_is_before(max_off, off_start_time))
goto fail; goto fail;
min_wait_time = off_start_time + off; }
max_wait_time = cur + off + timer_from_ns(300); 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 = timer_read_time(); n->last_req_time = cur;
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
gpio_out_write(pin, 0); n->last_req_time = cur;
n->last_req_time = timer_read_time();
return -1; return -1;
} }