Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test and adjust NeoPixel timings on multiple ports #6312

Merged
merged 1 commit into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions ports/atmel-samd/common-hal/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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};"
Expand All @@ -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
Expand All @@ -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;"
Expand Down
10 changes: 5 additions & 5 deletions ports/espressif/common-hal/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 7 additions & 4 deletions ports/nrf/common-hal/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 -------------
Expand Down
24 changes: 12 additions & 12 deletions ports/raspberrypi/common-hal/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand Down
4 changes: 2 additions & 2 deletions ports/stm/common-hal/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
36 changes: 36 additions & 0 deletions shared-bindings/neopixel_write/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down