Skip to content
Permalink
Browse files

Re-enable interrupts in between writing LED data for ARM M0. (#751)

* Fix breakage in NRF51 support introduced by #dba8825

* Re-enable interrupts in between writing LED data for ARM M0.

This adds support for FASTLED_ALLOW_INTERRUPTS to ARM M0-based platforms
(e.g. SAMD21).

This fixes the clock getting off when using more than ~30 LEDs, since
Arduino uses an interrupt to increment the millis clock.

This uses SysTick->VAL to determine whether more than 45uSecs have
elapsed while interrupts were enabled. This isn't as correct as using a
dedicated timer, but it does work on all ARM M0 platforms.
  • Loading branch information...
ademuri authored and focalintent committed Apr 6, 2019
1 parent 5535964 commit 403464a499a8feffa48f6f85d205550b9bc9c89b
Showing with 111 additions and 40 deletions.
  1. +110 −39 platforms/arm/common/m0clockless.h
  2. +1 −1 platforms/arm/d21/led_sysdefs_arm_d21.h
@@ -225,16 +225,17 @@ showLedData(volatile uint32_t *_port, uint32_t _bitmask, const uint8_t *_leds, u
#define CMPLOOP5 " cmploop5 %[counter], loop_%=;"
#define NOTHING ""

#if !(defined(SEI_CHK) && (FASTLED_ALLOW_INTERRUPTS == 1))
// We're not allowing interrupts - run the entire loop in asm to keep things
// as tight as possible. In an ideal world, we should be pushing out ws281x
// leds (or other 3-wire leds) with zero gaps between pixels.
#if (defined(SEI_CHK) && (FASTLED_ALLOW_INTERRUPTS == 1))
// We're allowing interrupts and have hardware timer support defined -
// track the loop outside the asm code, to allow inserting the interrupt
// overrun checks.
asm __volatile__ (
// pre-load byte 0
LOADLEDS3(0) LOADDITHER7(0) DITHER5 SCALE4(0) ADJDITHER7(0) SWAPBBN1
LOADLEDS3(0) LOADDITHER7(0) DITHER5 SCALE4(0) ADJDITHER7(0) SWAPBBN1
M0_ASM_ARGS);

// loop over writing out the data
LOOP
do {
asm __volatile__ (
// Write out byte 0, prepping byte 1
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(1) _D2(3) LO2 _D3(0)
@@ -252,24 +253,26 @@ showLedData(volatile uint32_t *_port, uint32_t _bitmask, const uint8_t *_leds, u
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(2) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(2) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 INCLEDS3 _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(0)

// Write out byte 2, prepping byte 0
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 INCLEDS3 _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(0) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(0) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(5) CMPLOOP5
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(5)

M0_ASM_ARGS
);
#else
// We're allowing interrupts - track the loop outside the asm code, to allow
// inserting the interrupt overrun checks.
);
SEI_CHK; INNER_SEI; --counter; CLI_CHK;
} while(counter);
#elif (FASTLED_ALLOW_INTERRUPTS == 1)
// We're allowing interrupts - track the loop outside the asm code, and
// re-enable interrupts in between each iteration.
asm __volatile__ (
// pre-load byte 0
LOADLEDS3(0) LOADDITHER7(0) DITHER5 SCALE4(0) ADJDITHER7(0) SWAPBBN1
@@ -278,39 +281,107 @@ showLedData(volatile uint32_t *_port, uint32_t _bitmask, const uint8_t *_leds, u
do {
asm __volatile__ (
// Write out byte 0, prepping byte 1
HI2 D1 QLO4 NOTHING D2(0) LO2 D3(0)
HI2 D1 QLO4 LOADLEDS3(1) D2(3) LO2 D3(0)
HI2 D1 QLO4 LOADDITHER7(1) D2(7) LO2 D3(0)
HI2 D1 QLO4 DITHER5 D2(5) LO2 D3(0)
HI2 D1 QLO4 SCALE4(1) D2(4) LO2 D3(0)
HI2 D1 QLO4 ADJDITHER7(1) D2(7) LO2 D3(0)
HI2 D1 QLO4 NOTHING D2(0) LO2 D3(0)
HI2 D1 QLO4 SWAPBBN1 D2(1) LO2 D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(1) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(1) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(1) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(1) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(0)

// Write out byte 1, prepping byte 2
HI2 D1 QLO4 NOTHING D2(0) LO2 D3(0)
HI2 D1 QLO4 LOADLEDS3(2) D2(3) LO2 D3(0)
HI2 D1 QLO4 LOADDITHER7(2) D2(7) LO2 D3(0)
HI2 D1 QLO4 DITHER5 D2(5) LO2 D3(0)
HI2 D1 QLO4 SCALE4(2) D2(4) LO2 D3(0)
HI2 D1 QLO4 ADJDITHER7(2) D2(7) LO2 D3(0)
HI2 D1 QLO4 NOTHING D2(0) LO2 D3(0)
HI2 D1 QLO4 SWAPBBN1 D2(1) LO2 D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(2) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(2) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(2) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(2) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 INCLEDS3 _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(0)

// Write out byte 2, prepping byte 0
HI2 D1 QLO4 INCLEDS3 D2(3) LO2 D3(0)
HI2 D1 QLO4 LOADLEDS3(0) D2(3) LO2 D3(0)
HI2 D1 QLO4 LOADDITHER7(0) D2(7) LO2 D3(0)
HI2 D1 QLO4 DITHER5 D2(5) LO2 D3(0)
HI2 D1 QLO4 SCALE4(0) D2(4) LO2 D3(0)
HI2 D1 QLO4 ADJDITHER7(0) D2(7) LO2 D3(0)
HI2 D1 QLO4 NOTHING D2(0) LO2 D3(0)
HI2 D1 QLO4 SWAPBBN1 D2(1) LO2 D3(5)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(0) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(0) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(5)

M0_ASM_ARGS
);
SEI_CHK; INNER_SEI; --counter; CLI_CHK;

uint32_t ticksBeforeInterrupts = SysTick->VAL;
sei();
--counter;
cli();

// If more than 45 uSecs have elapsed, give up on this frame and start over.
// Note: this isn't completely correct. It's possible that more than one
// millisecond will elapse, and so SysTick->VAL will lap
// ticksBeforeInterrupts.
// Note: ticksBeforeInterrupts DECREASES
const uint32_t kTicksPerMs = VARIANT_MCK / 1000;
const uint32_t kTicksPerUs = kTicksPerMs / 1000;
const uint32_t kTicksIn45us = kTicksPerUs * 45;

const uint32_t currentTicks = SysTick->VAL;

if (ticksBeforeInterrupts < currentTicks) {
// Timer started over
if ((ticksBeforeInterrupts + (kTicksPerMs - currentTicks)) > kTicksIn45us) {
return 0;
}
} else {
if ((ticksBeforeInterrupts - currentTicks) > kTicksIn45us) {
return 0;
}
}
} while(counter);
#else
// We're not allowing interrupts - run the entire loop in asm to keep things
// as tight as possible. In an ideal world, we should be pushing out ws281x
// leds (or other 3-wire leds) with zero gaps between pixels.
asm __volatile__ (
// pre-load byte 0
LOADLEDS3(0) LOADDITHER7(0) DITHER5 SCALE4(0) ADJDITHER7(0) SWAPBBN1

// loop over writing out the data
LOOP
// Write out byte 0, prepping byte 1
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(1) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(1) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(1) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(1) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(0)

// Write out byte 1, prepping byte 2
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(2) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(2) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(2) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(2) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 INCLEDS3 _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(0)

// Write out byte 2, prepping byte 0
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 LOADLEDS3(0) _D2(3) LO2 _D3(0)
HI2 _D1 QLO4 LOADDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 DITHER5 _D2(5) LO2 _D3(0)
HI2 _D1 QLO4 SCALE4(0) _D2(4) LO2 _D3(0)
HI2 _D1 QLO4 ADJDITHER7(0) _D2(7) LO2 _D3(0)
HI2 _D1 QLO4 NOTHING _D2(0) LO2 _D3(0)
HI2 _D1 QLO4 SWAPBBN1 _D2(1) LO2 _D3(5) CMPLOOP5

M0_ASM_ARGS
);
#endif
return num_leds;
}
@@ -11,7 +11,7 @@

// Default to allowing interrupts
#ifndef FASTLED_ALLOW_INTERRUPTS
#define FASTLED_ALLOW_INTERRUPTS 0
#define FASTLED_ALLOW_INTERRUPTS 1
#endif

#if FASTLED_ALLOW_INTERRUPTS == 1

0 comments on commit 403464a

Please sign in to comment.
You can’t perform that action at this time.