Skip to content

Commit

Permalink
SAMD51 (M4) - overclock compatible, uses SysTick
Browse files Browse the repository at this point in the history
  • Loading branch information
PaintYourDragon committed May 3, 2019
1 parent 8c8e38c commit 0a0b957
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 58 deletions.
102 changes: 46 additions & 56 deletions Adafruit_NeoPixel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1638,11 +1638,9 @@ void Adafruit_NeoPixel::show(void) {
}
#endif

#elif defined (__SAMD51__) // M4 @ 120mhz
// Tried this with a timer/counter, couldn't quite get adequate
// resolution. So yay, you get a load of goofball NOPs...
#elif defined (__SAMD51__) // M4

uint8_t *ptr, *end, p, bitMask, portNum;
uint8_t *ptr, *end, p, bitMask, portNum, bit;
uint32_t pinMask;

portNum = g_APinDescription[pin].ulPort;
Expand All @@ -1655,66 +1653,58 @@ void Adafruit_NeoPixel::show(void) {
volatile uint32_t *set = &(PORT->Group[portNum].OUTSET.reg),
*clr = &(PORT->Group[portNum].OUTCLR.reg);

// SAMD51 overclock-compatible timing is only a mild abomination.
// It uses SysTick for a consistent clock reference regardless of
// optimization / cache settings. That's the good news. The bad news,
// since SysTick->VAL is a volatile type it's slow to access...and then,
// with the SysTick interval that Arduino sets up (1 ms), this would
// require a subtract and MOD operation for gauging elapsed time, and
// all taken in combination that lacks adequate temporal resolution
// for NeoPixel timing. So a kind of horrible thing is done here...
// since interrupts are turned off anyway and it's generally accepted
// by now that we're gonna lose track of time in the NeoPixel lib,
// the SysTick timer is reconfigured for a period matching the NeoPixel
// bit timing (either 800 or 400 KHz) and we watch SysTick->VAL very
// closely (just a threshold, no subtract or MOD or anything) and that
// seems to work just well enough. When finished, the SysTick
// peripheral is set back to its original state.

uint32_t t0, t1, top, ticks,
saveLoad = SysTick->LOAD, saveVal = SysTick->VAL;

#ifdef NEO_KHZ400 // 800 KHz check needed only if 400 KHz support enabled
if(is800KHz) {
#endif
for(;;) {
if(p & bitMask) { // ONE
// High 800ns
*set = pinMask;
asm("nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;");
// Low 450ns
*clr = pinMask;
asm("nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop;");
} else { // ZERO
// High 400ns
*set = pinMask;
asm("nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop;");
// Low 850ns
*clr = pinMask;
asm("nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;"
"nop; nop; nop; nop; nop; nop; nop; nop;");
}
if(bitMask >>= 1) {
// Move on to the next pixel
asm("nop;");
} else {
if(ptr >= end) break;
p = *ptr++;
bitMask = 0x80;
}
}
top = (uint32_t)(F_CPU * 0.00000125); // Bit hi + lo = 1.25 uS
t0 = top - (uint32_t)(F_CPU * 0.00000040); // 0 = 0.4 uS hi
t1 = top - (uint32_t)(F_CPU * 0.00000080); // 1 = 0.8 uS hi
#ifdef NEO_KHZ400
} else { // 400 KHz bitstream
// ToDo!
top = (uint32_t)(F_CPU * 0.00000250); // Bit hi + lo = 2.5 uS
t0 = top - (uint32_t)(F_CPU * 0.00000050); // 0 = 0.5 uS hi
t1 = top - (uint32_t)(F_CPU * 0.00000120); // 1 = 1.2 uS hi
}
#endif

SysTick->LOAD = top; // Config SysTick for NeoPixel bit freq
SysTick->VAL = top; // Set to start value (counts down)

for(;;) {
*set = pinMask; // Set output high
ticks = (p & bitMask) ? t1 : t0; // SysTick threshold,
while(SysTick->VAL > ticks); // wait for it
*clr = pinMask; // Set output low
if(!(bitMask >>= 1)) { // Next bit for this byte...done?
if(ptr >= end) break; // If last byte sent, exit loop
p = *ptr++; // Fetch next byte
bitMask = 0x80; // Reset bitmask
}
while(SysTick->VAL <= ticks); // Wait for rollover to 'top'
}

SysTick->LOAD = saveLoad; // Restore SysTick rollover to 1 ms
SysTick->VAL = saveVal; // Restore SysTick value

#elif defined (ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz)

// Tried this with a timer/counter, couldn't quite get adequate
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,5 @@ Things I'd Like To Do But There's No Official Timeline So Please Don't Count On

* For the show() function (with all the delicate pixel timing stuff), break out each architecture into separate source files rather than the current unmaintainable tangle of #ifdef statements!
* Please don't use updateLength() or updateType() in new code. They should not have been implemented this way (use the C++ 'new' operator with the regular constructor instead) and are only sticking around because of the Prime Directive. setPin() is OK for now though, it's a trick we can use to 'recycle' pixel memory across multiple strips.
* In the M0 and M4 code, use the hardware systick counter for bit timing rather than hand-tweaked NOPs (a temporary kludge at the time because I wasn't reading systick correctly).
* In the M0 and M4 code, use the hardware systick counter for bit timing rather than hand-tweaked NOPs (a temporary kludge at the time because I wasn't reading systick correctly). (As of 1.4.2, systick is used on M4 devices and it appears to be overclock-compatible. Not for M0 yet, which is why this item is still here.)
* As currently written, brightness scaling is still a "destructive" operation -- pixel values are altered in RAM and the original value as set can't be accurately read back, only approximated, which has been confusing and frustrating to users. It was done this way at the time because NeoPixel timing is strict, AVR microcontrollers (all we had at the time) are limited, and assembly language is hard. All the 32-bit architectures should have no problem handling nondestructive brightness scaling -- calculating each byte immediately before it's sent out the wire, maintaining the original set value in RAM -- the work just hasn't been done. There's a fair chance even the AVR code could manage it with some intense focus. (The DotStar library achieves nondestructive brightness scaling because it doesn't have to manage data timing so carefully...every architecture, even ATtiny, just takes whatever cycles it needs for the multiply/shift operations.)
2 changes: 1 addition & 1 deletion library.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name=Adafruit NeoPixel
version=1.2.1
version=1.2.2
author=Adafruit
maintainer=Adafruit <[email protected]>
sentence=Arduino library for controlling single-wire-based LED pixels and strip.
Expand Down

0 comments on commit 0a0b957

Please sign in to comment.