diff --git a/ports/atmel-samd/common-hal/neopixel_write/__init__.c b/ports/atmel-samd/common-hal/neopixel_write/__init__.c index 182b8eee14..824bc5e3ed 100644 --- a/ports/atmel-samd/common-hal/neopixel_write/__init__.c +++ b/ports/atmel-samd/common-hal/neopixel_write/__init__.c @@ -46,6 +46,9 @@ __attribute__((naked,noinline,aligned(16))) static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask, const uint8_t *ptr, int numBytes); +// The SAMD21 timing loop durations below are approximate, +// because the other instructions take significant time. + static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask, const uint8_t *ptr, int numBytes) { asm volatile (" push {r4, r5, r6, lr};" @@ -54,25 +57,28 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa " ldrb r5, [r2, #0];" // r5 := *ptr " add r2, #1;" // ptr++ " movs r4, #128;" // r4-mask, 0x80 + "loopBit:" " str r1, [r0, #4];" // set #ifdef SAMD21 - " movs r6, #3; d2: sub r6, #1; bne d2;" // delay 3 + " movs r6, #2; d2: sub r6, #1; bne d2;" // 248 ns high (entire T0H or start T1H) #endif #ifdef SAM_D5X_E5X - " movs r6, #16; d2: subs r6, #1; bne d2;" // delay 3 + " movs r6, #11; d2: subs r6, #1; bne d2;" // 300 ns high (entire T0H or start T1H) #endif " tst r4, r5;" // mask&r5 " bne skipclr;" " str r1, [r0, #0];" // clr + "skipclr:" #ifdef SAMD21 - " movs r6, #6; d0: sub r6, #1; bne d0;" // delay 6 + " movs r6, #7; d0: sub r6, #1; bne d0;" // 772 ns low or high (start T0L or end T1H) #endif #ifdef SAM_D5X_E5X - " movs r6, #16; d0: subs r6, #1; bne d0;" // delay 6 + " movs r6, #15; d0: subs r6, #1; bne d0;" // 388 ns low or high (start T0L or end T1H) #endif " str r1, [r0, #0];" // clr (possibly again, doesn't matter) + #ifdef SAMD21 " asr r4, r4, #1;" // mask >>= 1 #endif @@ -82,15 +88,20 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa " beq nextbyte;" " uxtb r4, r4;" #ifdef SAMD21 - " movs r6, #2; d1: sub r6, #1; bne d1;" // delay 2 + " movs r6, #5; d1: sub r6, #1; bne d1;" // 496 ns (end TOL or entire T1L) #endif #ifdef SAM_D5X_E5X - " movs r6, #15; d1: subs r6, #1; bne d1;" // delay 2 + " movs r6, #20; d1: subs r6, #1; bne d1;" // 548 ns (end TOL or entire T1L) #endif " b loopBit;" + "nextbyte:" - #ifdef SAM_D5X_E5X - " movs r6, #12; d3: subs r6, #1; bne d3;" // delay 2 + #ifdef SAMD21 + " movs r6, #1; d3: sub r6, #1; bne d3;" // 60 ns (end TOL or entire T1L) + // other instructions add more delay + #endif + #ifdef SAM_D5X_E5X + " movs r6, #18; d3: subs r6, #1; bne d3;" // extra for 936 ns total (byte end T0L or entire T1L) #endif " cmp r2, r3;" " bcs neopixel_stop;" diff --git a/ports/espressif/common-hal/neopixel_write/__init__.c b/ports/espressif/common-hal/neopixel_write/__init__.c index b140eff4f9..148b47aeb0 100644 --- a/ports/espressif/common-hal/neopixel_write/__init__.c +++ b/ports/espressif/common-hal/neopixel_write/__init__.c @@ -47,11 +47,11 @@ #include "components/driver/include/driver/rmt.h" #include "peripherals/rmt.h" -// 416 ns is 1/3 of the 1250ns period of a 800khz signal. -#define WS2812_T0H_NS (416) -#define WS2812_T0L_NS (416 * 2) -#define WS2812_T1H_NS (416 * 2) -#define WS2812_T1L_NS (416) +// Use closer to WS2812-style timings instead of WS2812B, to accommodate more varieties. +#define WS2812_T0H_NS (316) +#define WS2812_T0L_NS (316 * 3) +#define WS2812_T1H_NS (700) +#define WS2812_T1L_NS (564) static uint32_t ws2812_t0h_ticks = 0; static uint32_t ws2812_t1h_ticks = 0; diff --git a/ports/nrf/common-hal/neopixel_write/__init__.c b/ports/nrf/common-hal/neopixel_write/__init__.c index 6f012fa35b..ce8ee8c7cf 100644 --- a/ports/nrf/common-hal/neopixel_write/__init__.c +++ b/ports/nrf/common-hal/neopixel_write/__init__.c @@ -53,13 +53,16 @@ // The PWM starts the duty cycle in LOW. To start with HIGH we // need to set the 15th bit on each register. +// *** CircuitPython: Use WS2812 for all, works with https://adafru.it/5225 and everything else +// *** + // WS2812 (rev A) timing is 0.35 and 0.7us -// #define MAGIC_T0H 5UL | (0x8000) // 0.3125us -// #define MAGIC_T1H 12UL | (0x8000) // 0.75us +#define MAGIC_T0H 5UL | (0x8000) // 0.3125us +#define MAGIC_T1H 12UL | (0x8000) // 0.75us // WS2812B (rev B) timing is 0.4 and 0.8 us -#define MAGIC_T0H 6UL | (0x8000) // 0.375us -#define MAGIC_T1H 13UL | (0x8000) // 0.8125us +// #define MAGIC_T0H 6UL | (0x8000) // 0.375us +// #define MAGIC_T1H 13UL | (0x8000) // 0.8125us #define CTOPVAL 20UL // 1.25us // ---------- END Constants for the EasyDMA implementation ------------- diff --git a/ports/raspberrypi/common-hal/neopixel_write/__init__.c b/ports/raspberrypi/common-hal/neopixel_write/__init__.c index 1acfed055c..034c5de002 100644 --- a/ports/raspberrypi/common-hal/neopixel_write/__init__.c +++ b/ports/raspberrypi/common-hal/neopixel_write/__init__.c @@ -35,23 +35,23 @@ uint64_t next_start_raw_ticks = 0; -// NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones -// are 2/3 duty cycle (~833ns). Each of the instructions below take 1/3 duty +// NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> and ones +// and ones as <700 ns hi, 556 ns lo>. // cycle. The first two instructions always run while only one of the two final // instructions run per bit. We start with the low period because it can be -// longer than 1/3 period while waiting for more data. +// longer while waiting for more data. const uint16_t neopixel_program[] = { // bitloop: -// out x 1 side 0 [1]; Side-set still takes place before instruction stalls - 0x6121, -// jmp !x do_zero side 1 [1]; Branch on the bit we shifted out after 1/3 duty delay. Positive pulse - 0x1123, +// out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls. + 0x6621, +// jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high. + 0x1323, // do_one: -// jmp bitloop side 1 [1]; Continue driving high, for a long pulse - 0x1100, +// jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse) + 0x1400, // do_zero: -// nop side 0 [1]; Or drive low, for a short pulse - 0xa142 +// nop side 0 [4]; Or drive low, for a zero (short pulse) + 0xa442 }; void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t num_bytes) { @@ -63,7 +63,7 @@ void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint32_t pins_we_use = 1 << digitalinout->pin->number; bool ok = rp2pio_statemachine_construct(&state_machine, neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]), - 800000 * 6, // 800 khz * 6 cycles per bit + 12800000, // MHz, to get about appropriate sub-bit times in PIO program. NULL, 0, // init program NULL, 1, // out NULL, 1, // in diff --git a/ports/stm/common-hal/neopixel_write/__init__.c b/ports/stm/common-hal/neopixel_write/__init__.c index f5dabe7f99..99529793fa 100644 --- a/ports/stm/common-hal/neopixel_write/__init__.c +++ b/ports/stm/common-hal/neopixel_write/__init__.c @@ -38,8 +38,8 @@ uint64_t next_start_raw_ticks = 0; // sysclock divisors #define MAGIC_800_INT 900000 // ~1.11 us -> 1.2 field -#define MAGIC_800_T0H 2800000 // ~0.36 us -> 0.44 field -#define MAGIC_800_T1H 1350000 // ~0.74 us -> 0.84 field +#define MAGIC_800_T0H 3500000 // 300ns actual; 880 low +#define MAGIC_800_T1H 1350000 // 768ns actual; 412 low #pragma GCC push_options #pragma GCC optimize ("Os") diff --git a/shared-bindings/neopixel_write/__init__.c b/shared-bindings/neopixel_write/__init__.c index a95020ad46..bcdedf62a7 100644 --- a/shared-bindings/neopixel_write/__init__.c +++ b/shared-bindings/neopixel_write/__init__.c @@ -32,6 +32,42 @@ #include "shared-bindings/util.h" #include "supervisor/shared/translate.h" +// RGB LED timing information: + +// From the WS2811 datasheet: high speed mode +// - T0H 0 code,high voltage time 0.25 us +-150ns +// - T1H 1 code,high voltage time 0.6 us +-150ns +// - T0L 0 code,low voltage time 1.0 us +-150ns +// - T1L 1 code,low voltage time 0.65 us +-150ns +// - RES low voltage time Above 50us + +// From the SK6812 datasheet: +// - T0H 0 code, high level time 0.3us +-0.15us +// - T1H 1 code, high level time 0.6us +-0.15us +// - T0L 0 code, low level time 0.9us +-0.15us +// - T1L 1 code, low level time 0.6us +-0.15us +// - Trst Reset code,low level time 80us + +// From the WS2812 datasheet: +// - T0H 0 code, high voltage time 0.35us +-150ns +// - T1H 1 code, high voltage time 0.7us +-150ns +// - T0L 0 code, low voltage time 0.8us +-150ns +// - T1L 1 code, low voltage time 0.6us +-150ns +// - RES low voltage time Above 50us + +// From the WS28212B datasheet: +// - T0H 0 code, high voltage time 0.4us +-150ns +// - T1H 1 code, high voltage time 0.8us +-150ns +// - T0L 0 code, low voltage time 0.85us +-150ns +// - T1L 1 code, low voltage time 0.45us +-150ns +// - RES low voltage time Above 50us + +// The timings used in various ports do not always follow the guidelines above. +// In general, a zero bit is about 300ns high, 900ns low. +// A one bit is about 700ns high, 500ns low. +// But the ports vary based on implementation considerations; the proof is in the testing. +// https://adafru.it/5225 is more sensitive to timing and should be included in testing. + STATIC void check_for_deinit(digitalio_digitalinout_obj_t *self) { if (common_hal_digitalio_digitalinout_deinited(self)) { raise_deinited_error();