Merge pull request #6312 from dhalbert/adjust-neopixel-timings
test and adjust NeoPixel timings on multiple ports
This commit is contained in:
commit
63abd9a9cf
@ -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 SAM_D5X_E5X
|
#ifdef SAMD21
|
||||||
" movs r6, #12; d3: subs r6, #1; bne d3;" // delay 2
|
" 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
|
#endif
|
||||||
" cmp r2, r3;"
|
" cmp r2, r3;"
|
||||||
" bcs neopixel_stop;"
|
" bcs neopixel_stop;"
|
||||||
|
@ -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;
|
||||||
|
@ -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 -------------
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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 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) {
|
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();
|
||||||
|
Loading…
Reference in New Issue
Block a user