Merge pull request #6312 from dhalbert/adjust-neopixel-timings

test and adjust NeoPixel timings on multiple ports
This commit is contained in:
Dan Halbert 2022-04-26 13:18:47 -04:00 committed by GitHub
commit 63abd9a9cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 31 deletions

View File

@ -46,6 +46,9 @@ __attribute__((naked,noinline,aligned(16)))
static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask, static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask,
const uint8_t *ptr, int numBytes); 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, static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMask,
const uint8_t *ptr, int numBytes) { const uint8_t *ptr, int numBytes) {
asm volatile (" push {r4, r5, r6, lr};" 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 " ldrb r5, [r2, #0];" // r5 := *ptr
" add r2, #1;" // ptr++ " add r2, #1;" // ptr++
" movs r4, #128;" // r4-mask, 0x80 " movs r4, #128;" // r4-mask, 0x80
"loopBit:" "loopBit:"
" str r1, [r0, #4];" // set " str r1, [r0, #4];" // set
#ifdef SAMD21 #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 #endif
#ifdef SAM_D5X_E5X #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 #endif
" tst r4, r5;" // mask&r5 " tst r4, r5;" // mask&r5
" bne skipclr;" " bne skipclr;"
" str r1, [r0, #0];" // clr " str r1, [r0, #0];" // clr
"skipclr:" "skipclr:"
#ifdef SAMD21 #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 #endif
#ifdef SAM_D5X_E5X #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 #endif
" str r1, [r0, #0];" // clr (possibly again, doesn't matter) " str r1, [r0, #0];" // clr (possibly again, doesn't matter)
#ifdef SAMD21 #ifdef SAMD21
" asr r4, r4, #1;" // mask >>= 1 " asr r4, r4, #1;" // mask >>= 1
#endif #endif
@ -82,15 +88,20 @@ static void neopixel_send_buffer_core(volatile uint32_t *clraddr, uint32_t pinMa
" beq nextbyte;" " beq nextbyte;"
" uxtb r4, r4;" " uxtb r4, r4;"
#ifdef SAMD21 #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 #endif
#ifdef SAM_D5X_E5X #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 #endif
" b loopBit;" " b loopBit;"
"nextbyte:" "nextbyte:"
#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 #ifdef SAM_D5X_E5X
" movs r6, #12; d3: subs r6, #1; bne d3;" // delay 2 " movs r6, #18; d3: subs r6, #1; bne d3;" // extra for 936 ns total (byte end T0L or entire T1L)
#endif #endif
" cmp r2, r3;" " cmp r2, r3;"
" bcs neopixel_stop;" " bcs neopixel_stop;"

View File

@ -47,11 +47,11 @@
#include "components/driver/include/driver/rmt.h" #include "components/driver/include/driver/rmt.h"
#include "peripherals/rmt.h" #include "peripherals/rmt.h"
// 416 ns is 1/3 of the 1250ns period of a 800khz signal. // Use closer to WS2812-style timings instead of WS2812B, to accommodate more varieties.
#define WS2812_T0H_NS (416) #define WS2812_T0H_NS (316)
#define WS2812_T0L_NS (416 * 2) #define WS2812_T0L_NS (316 * 3)
#define WS2812_T1H_NS (416 * 2) #define WS2812_T1H_NS (700)
#define WS2812_T1L_NS (416) #define WS2812_T1L_NS (564)
static uint32_t ws2812_t0h_ticks = 0; static uint32_t ws2812_t0h_ticks = 0;
static uint32_t ws2812_t1h_ticks = 0; static uint32_t ws2812_t1h_ticks = 0;

View File

@ -53,13 +53,16 @@
// The PWM starts the duty cycle in LOW. To start with HIGH we // The PWM starts the duty cycle in LOW. To start with HIGH we
// need to set the 15th bit on each register. // 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 // WS2812 (rev A) timing is 0.35 and 0.7us
// #define MAGIC_T0H 5UL | (0x8000) // 0.3125us #define MAGIC_T0H 5UL | (0x8000) // 0.3125us
// #define MAGIC_T1H 12UL | (0x8000) // 0.75us #define MAGIC_T1H 12UL | (0x8000) // 0.75us
// WS2812B (rev B) timing is 0.4 and 0.8 us // WS2812B (rev B) timing is 0.4 and 0.8 us
#define MAGIC_T0H 6UL | (0x8000) // 0.375us // #define MAGIC_T0H 6UL | (0x8000) // 0.375us
#define MAGIC_T1H 13UL | (0x8000) // 0.8125us // #define MAGIC_T1H 13UL | (0x8000) // 0.8125us
#define CTOPVAL 20UL // 1.25us #define CTOPVAL 20UL // 1.25us
// ---------- END Constants for the EasyDMA implementation ------------- // ---------- END Constants for the EasyDMA implementation -------------

View File

@ -35,23 +35,23 @@
uint64_t next_start_raw_ticks = 0; uint64_t next_start_raw_ticks = 0;
// NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones // NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> and ones
// are 2/3 duty cycle (~833ns). Each of the instructions below take 1/3 duty // and ones as <700 ns hi, 556 ns lo>.
// cycle. The first two instructions always run while only one of the two final // 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 // 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[] = { const uint16_t neopixel_program[] = {
// bitloop: // bitloop:
// out x 1 side 0 [1]; Side-set still takes place before instruction stalls // out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls.
0x6121, 0x6621,
// jmp !x do_zero side 1 [1]; Branch on the bit we shifted out after 1/3 duty delay. Positive pulse // jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high.
0x1123, 0x1323,
// do_one: // do_one:
// jmp bitloop side 1 [1]; Continue driving high, for a long pulse // jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse)
0x1100, 0x1400,
// do_zero: // do_zero:
// nop side 0 [1]; Or drive low, for a short pulse // nop side 0 [4]; Or drive low, for a zero (short pulse)
0xa142 0xa442
}; };
void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t num_bytes) { 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; uint32_t pins_we_use = 1 << digitalinout->pin->number;
bool ok = rp2pio_statemachine_construct(&state_machine, bool ok = rp2pio_statemachine_construct(&state_machine,
neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]), 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, 0, // init program
NULL, 1, // out NULL, 1, // out
NULL, 1, // in NULL, 1, // in

View File

@ -38,8 +38,8 @@ uint64_t next_start_raw_ticks = 0;
// sysclock divisors // sysclock divisors
#define MAGIC_800_INT 900000 // ~1.11 us -> 1.2 field #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_T0H 3500000 // 300ns actual; 880 low
#define MAGIC_800_T1H 1350000 // ~0.74 us -> 0.84 field #define MAGIC_800_T1H 1350000 // 768ns actual; 412 low
#pragma GCC push_options #pragma GCC push_options
#pragma GCC optimize ("Os") #pragma GCC optimize ("Os")

View File

@ -32,6 +32,42 @@
#include "shared-bindings/util.h" #include "shared-bindings/util.h"
#include "supervisor/shared/translate.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 codelow 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) { STATIC void check_for_deinit(digitalio_digitalinout_obj_t *self) {
if (common_hal_digitalio_digitalinout_deinited(self)) { if (common_hal_digitalio_digitalinout_deinited(self)) {
raise_deinited_error(); raise_deinited_error();