Skip to content

Commit 63abd9a

Browse files
authored
Merge pull request #6312 from dhalbert/adjust-neopixel-timings
test and adjust NeoPixel timings on multiple ports
2 parents 3a8fb4e + 4eeaf41 commit 63abd9a

File tree

6 files changed

+81
-31
lines changed

6 files changed

+81
-31
lines changed

ports/atmel-samd/common-hal/neopixel_write/__init__.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ __attribute__((naked,noinline,aligned(16)))
4646
static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask,
4747
const uint8_t *ptr, int numBytes);
4848

49+
// The SAMD21 timing loop durations below are approximate,
50+
// because the other instructions take significant time.
51+
4952
static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask,
5053
const uint8_t *ptr, int numBytes) {
5154
asm volatile (" push {r4, r5, r6, lr};"
@@ -54,25 +57,28 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa
5457
" ldrb r5, [r2, #0];" // r5 := *ptr
5558
" add r2, #1;" // ptr++
5659
" movs r4, #128;" // r4-mask, 0x80
60+
5761
"loopBit:"
5862
" str r1, [r0, #4];" // set
5963
#ifdef SAMD21
60-
" movs r6, #3; d2: sub r6, #1; bne d2;" // delay 3
64+
" movs r6, #2; d2: sub r6, #1; bne d2;" // 248 ns high (entire T0H or start T1H)
6165
#endif
6266
#ifdef SAM_D5X_E5X
63-
" movs r6, #16; d2: subs r6, #1; bne d2;" // delay 3
67+
" movs r6, #11; d2: subs r6, #1; bne d2;" // 300 ns high (entire T0H or start T1H)
6468
#endif
6569
" tst r4, r5;" // mask&r5
6670
" bne skipclr;"
6771
" str r1, [r0, #0];" // clr
72+
6873
"skipclr:"
6974
#ifdef SAMD21
70-
" movs r6, #6; d0: sub r6, #1; bne d0;" // delay 6
75+
" movs r6, #7; d0: sub r6, #1; bne d0;" // 772 ns low or high (start T0L or end T1H)
7176
#endif
7277
#ifdef SAM_D5X_E5X
73-
" movs r6, #16; d0: subs r6, #1; bne d0;" // delay 6
78+
" movs r6, #15; d0: subs r6, #1; bne d0;" // 388 ns low or high (start T0L or end T1H)
7479
#endif
7580
" str r1, [r0, #0];" // clr (possibly again, doesn't matter)
81+
7682
#ifdef SAMD21
7783
" asr r4, r4, #1;" // mask >>= 1
7884
#endif
@@ -82,15 +88,20 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa
8288
" beq nextbyte;"
8389
" uxtb r4, r4;"
8490
#ifdef SAMD21
85-
" movs r6, #2; d1: sub r6, #1; bne d1;" // delay 2
91+
" movs r6, #5; d1: sub r6, #1; bne d1;" // 496 ns (end TOL or entire T1L)
8692
#endif
8793
#ifdef SAM_D5X_E5X
88-
" movs r6, #15; d1: subs r6, #1; bne d1;" // delay 2
94+
" movs r6, #20; d1: subs r6, #1; bne d1;" // 548 ns (end TOL or entire T1L)
8995
#endif
9096
" b loopBit;"
97+
9198
"nextbyte:"
92-
#ifdef SAM_D5X_E5X
93-
" movs r6, #12; d3: subs r6, #1; bne d3;" // delay 2
99+
#ifdef SAMD21
100+
" movs r6, #1; d3: sub r6, #1; bne d3;" // 60 ns (end TOL or entire T1L)
101+
// other instructions add more delay
102+
#endif
103+
#ifdef SAM_D5X_E5X
104+
" movs r6, #18; d3: subs r6, #1; bne d3;" // extra for 936 ns total (byte end T0L or entire T1L)
94105
#endif
95106
" cmp r2, r3;"
96107
" bcs neopixel_stop;"

ports/espressif/common-hal/neopixel_write/__init__.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@
4747
#include "components/driver/include/driver/rmt.h"
4848
#include "peripherals/rmt.h"
4949

50-
// 416 ns is 1/3 of the 1250ns period of a 800khz signal.
51-
#define WS2812_T0H_NS (416)
52-
#define WS2812_T0L_NS (416 * 2)
53-
#define WS2812_T1H_NS (416 * 2)
54-
#define WS2812_T1L_NS (416)
50+
// Use closer to WS2812-style timings instead of WS2812B, to accommodate more varieties.
51+
#define WS2812_T0H_NS (316)
52+
#define WS2812_T0L_NS (316 * 3)
53+
#define WS2812_T1H_NS (700)
54+
#define WS2812_T1L_NS (564)
5555

5656
static uint32_t ws2812_t0h_ticks = 0;
5757
static uint32_t ws2812_t1h_ticks = 0;

ports/nrf/common-hal/neopixel_write/__init__.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,16 @@
5353
// The PWM starts the duty cycle in LOW. To start with HIGH we
5454
// need to set the 15th bit on each register.
5555

56+
// *** CircuitPython: Use WS2812 for all, works with https://adafru.it/5225 and everything else
57+
// ***
58+
5659
// WS2812 (rev A) timing is 0.35 and 0.7us
57-
// #define MAGIC_T0H 5UL | (0x8000) // 0.3125us
58-
// #define MAGIC_T1H 12UL | (0x8000) // 0.75us
60+
#define MAGIC_T0H 5UL | (0x8000) // 0.3125us
61+
#define MAGIC_T1H 12UL | (0x8000) // 0.75us
5962

6063
// WS2812B (rev B) timing is 0.4 and 0.8 us
61-
#define MAGIC_T0H 6UL | (0x8000) // 0.375us
62-
#define MAGIC_T1H 13UL | (0x8000) // 0.8125us
64+
// #define MAGIC_T0H 6UL | (0x8000) // 0.375us
65+
// #define MAGIC_T1H 13UL | (0x8000) // 0.8125us
6366
#define CTOPVAL 20UL // 1.25us
6467

6568
// ---------- END Constants for the EasyDMA implementation -------------

ports/raspberrypi/common-hal/neopixel_write/__init__.c

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,23 +35,23 @@
3535

3636
uint64_t next_start_raw_ticks = 0;
3737

38-
// NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones
39-
// are 2/3 duty cycle (~833ns). Each of the instructions below take 1/3 duty
38+
// NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> and ones
39+
// and ones as <700 ns hi, 556 ns lo>.
4040
// cycle. The first two instructions always run while only one of the two final
4141
// instructions run per bit. We start with the low period because it can be
42-
// longer than 1/3 period while waiting for more data.
42+
// longer while waiting for more data.
4343
const uint16_t neopixel_program[] = {
4444
// bitloop:
45-
// out x 1 side 0 [1]; Side-set still takes place before instruction stalls
46-
0x6121,
47-
// jmp !x do_zero side 1 [1]; Branch on the bit we shifted out after 1/3 duty delay. Positive pulse
48-
0x1123,
45+
// out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls.
46+
0x6621,
47+
// jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high.
48+
0x1323,
4949
// do_one:
50-
// jmp bitloop side 1 [1]; Continue driving high, for a long pulse
51-
0x1100,
50+
// jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse)
51+
0x1400,
5252
// do_zero:
53-
// nop side 0 [1]; Or drive low, for a short pulse
54-
0xa142
53+
// nop side 0 [4]; Or drive low, for a zero (short pulse)
54+
0xa442
5555
};
5656

5757
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,
6363
uint32_t pins_we_use = 1 << digitalinout->pin->number;
6464
bool ok = rp2pio_statemachine_construct(&state_machine,
6565
neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]),
66-
800000 * 6, // 800 khz * 6 cycles per bit
66+
12800000, // MHz, to get about appropriate sub-bit times in PIO program.
6767
NULL, 0, // init program
6868
NULL, 1, // out
6969
NULL, 1, // in

ports/stm/common-hal/neopixel_write/__init__.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ uint64_t next_start_raw_ticks = 0;
3838

3939
// sysclock divisors
4040
#define MAGIC_800_INT 900000 // ~1.11 us -> 1.2 field
41-
#define MAGIC_800_T0H 2800000 // ~0.36 us -> 0.44 field
42-
#define MAGIC_800_T1H 1350000 // ~0.74 us -> 0.84 field
41+
#define MAGIC_800_T0H 3500000 // 300ns actual; 880 low
42+
#define MAGIC_800_T1H 1350000 // 768ns actual; 412 low
4343

4444
#pragma GCC push_options
4545
#pragma GCC optimize ("Os")

shared-bindings/neopixel_write/__init__.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,42 @@
3232
#include "shared-bindings/util.h"
3333
#include "supervisor/shared/translate.h"
3434

35+
// RGB LED timing information:
36+
37+
// From the WS2811 datasheet: high speed mode
38+
// - T0H 0 code,high voltage time 0.25 us +-150ns
39+
// - T1H 1 code,high voltage time 0.6 us +-150ns
40+
// - T0L 0 code,low voltage time 1.0 us +-150ns
41+
// - T1L 1 code,low voltage time 0.65 us +-150ns
42+
// - RES low voltage time Above 50us
43+
44+
// From the SK6812 datasheet:
45+
// - T0H 0 code, high level time 0.3us +-0.15us
46+
// - T1H 1 code, high level time 0.6us +-0.15us
47+
// - T0L 0 code, low level time 0.9us +-0.15us
48+
// - T1L 1 code, low level time 0.6us +-0.15us
49+
// - Trst Reset code,low level time 80us
50+
51+
// From the WS2812 datasheet:
52+
// - T0H 0 code, high voltage time 0.35us +-150ns
53+
// - T1H 1 code, high voltage time 0.7us +-150ns
54+
// - T0L 0 code, low voltage time 0.8us +-150ns
55+
// - T1L 1 code, low voltage time 0.6us +-150ns
56+
// - RES low voltage time Above 50us
57+
58+
// From the WS28212B datasheet:
59+
// - T0H 0 code, high voltage time 0.4us +-150ns
60+
// - T1H 1 code, high voltage time 0.8us +-150ns
61+
// - T0L 0 code, low voltage time 0.85us +-150ns
62+
// - T1L 1 code, low voltage time 0.45us +-150ns
63+
// - RES low voltage time Above 50us
64+
65+
// The timings used in various ports do not always follow the guidelines above.
66+
// In general, a zero bit is about 300ns high, 900ns low.
67+
// A one bit is about 700ns high, 500ns low.
68+
// But the ports vary based on implementation considerations; the proof is in the testing.
69+
// https://adafru.it/5225 is more sensitive to timing and should be included in testing.
70+
3571
STATIC void check_for_deinit(digitalio_digitalinout_obj_t *self) {
3672
if (common_hal_digitalio_digitalinout_deinited(self)) {
3773
raise_deinited_error();

0 commit comments

Comments
 (0)