From 1cf6b3187b1a78c532f79c952e4d89f199c194c4 Mon Sep 17 00:00:00 2001 From: Michael Miller Date: Mon, 26 Jun 2023 06:32:45 -0700 Subject: [PATCH] Avr APA106 (#721) * Apa106 timing support for 600Kbps * interpixel time by speed object --- src/internal/methods/NeoAvrMethod.h | 105 +++++++++++++++++++++++++++- src/internal/methods/NeoPixelAvr.c | 78 +++++++++++++++++++-- 2 files changed, 177 insertions(+), 6 deletions(-) diff --git a/src/internal/methods/NeoAvrMethod.h b/src/internal/methods/NeoAvrMethod.h index 8dc0e9b..c72b993 100644 --- a/src/internal/methods/NeoAvrMethod.h +++ b/src/internal/methods/NeoAvrMethod.h @@ -39,6 +39,7 @@ extern "C" void send_data_12mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask); void send_data_16mhz_800(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask); void send_data_16mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask); + void send_data_16mhz_600(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask); void send_data_32mhz(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask, const uint8_t cycleTiming); } @@ -74,6 +75,38 @@ public: }; +class NeoAvrSpeed600KbpsBase +{ +public: + static void send_data(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask) + { +#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) // 8Mhz CPU +#ifdef PORTD // PORTD isn't present on ATtiny85, etc. + if (port == &PORTD) + send_data_8mhz_800_PortD(data, sizeData, pinMask); + else if (port == &PORTB) +#endif // PORTD + send_data_8mhz_800_PortB(data, sizeData, pinMask); + +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) // 12Mhz CPU +#ifdef PORTD // PORTD + if (port == &PORTD) + send_data_12mhz_800_PortD(data, sizeData, pinMask); + else if (port == &PORTB) +#endif // PORTD + send_data_12mhz_800_PortB(data, sizeData, pinMask); + +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000UL) // 16Mhz CPU + send_data_16mhz_600(data, sizeData, port, pinMask); +#elif (F_CPU >= 31000000UL) && (F_CPU <= 35000000UL) // 32Mhz CPU + send_data_32mhz(data, sizeData, port, pinMask, 3); +#else +#error "CPU SPEED NOT SUPPORTED" +#endif + } + +}; + class NeoAvrSpeedWs2812x : public NeoAvrSpeed800KbpsBase { public: @@ -86,6 +119,19 @@ public: static const uint32_t ResetTimeUs = 80; }; +class NeoAvrSpeedApa106 : public NeoAvrSpeed600KbpsBase +{ +public: + static const uint32_t ResetTimeUs = 100; +}; + +class NeoAvrSpeed600KbpsIps : public NeoAvrSpeed600KbpsBase +{ +public: + static const uint32_t ResetTimeUs = 300; + static const uint16_t InterpixelTimeUs = 8; // 12.4, with loop overhead of about 5us for loop +}; + class NeoAvrSpeedTm1814 : public NeoAvrSpeed800KbpsBase { public: @@ -212,7 +258,7 @@ public: { } -private: +protected: const size_t _sizeData; // size of _data below const uint8_t _pin; // output pin number @@ -223,9 +269,64 @@ private: uint8_t _pinMask; // Output PORT bitmask }; +template class NeoAvrIpsMethodBase : public NeoAvrMethodBase +{ +public: + NeoAvrIpsMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize, size_t settingsSize) : + NeoAvrMethodBase(pin, pixelCount, elementSize, settingsSize), + _elementSize(elementSize) + { + } + + ~NeoAvrIpsMethodBase() + { + } + + void Update(bool) + { + // Data latch = 50+ microsecond pause in the output stream. Rather than + // put a delay at the end of the function, the ending time is noted and + // the function will simply hold off (if needed) on issuing the + // subsequent round of data until the latch time has elapsed. This + // allows the mainline code to start generating the next frame of data + // rather than stalling for the latch. + while (!NeoAvrMethodBase::IsReadyToUpdate()) + { +#if !defined(ARDUINO_TEEONARDU_LEO) && !defined(ARDUINO_TEEONARDU_FLORA) + yield(); // allows for system yield if needed +#endif + } + + noInterrupts(); // Need 100% focus on instruction timing + + uint8_t* dataPixel = NeoAvrMethodBase::_data; + const uint8_t* dataEnd = dataPixel + NeoAvrMethodBase::_sizeData; + + while (dataPixel < dataEnd) + { + T_SPEED::send_data(dataPixel, + _elementSize, + NeoAvrMethodBase::_port, + NeoAvrMethodBase::_pinMask); + dataPixel += _elementSize; + delayMicroseconds(T_SPEED::InterpixelTimeUs); + } + + interrupts(); + + // save EOD time for latch on next call + NeoAvrMethodBase::_endTime = micros(); + } + +private: + const size_t _elementSize; // size of a single pixel +}; typedef NeoAvrMethodBase NeoAvrWs2812xMethod; typedef NeoAvrMethodBase NeoAvrSk6812Method; +typedef NeoAvrMethodBase NeoAvrApa106Method; +typedef NeoAvrIpsMethodBase NeoAvr600KbpsIpsMethod; + typedef NeoAvrMethodBase NeoAvrTm1814InvertedMethod; typedef NeoAvrMethodBase NeoAvrTm1829InvertedMethod; typedef NeoAvrMethodBase NeoAvr800KbpsMethod; @@ -240,7 +341,7 @@ typedef NeoAvrWs2812xMethod NeoWs2811Method; typedef NeoAvrWs2812xMethod NeoWs2816Method; typedef NeoAvrSk6812Method NeoSk6812Method; typedef NeoAvrSk6812Method NeoLc8812Method; -typedef NeoAvr400KbpsMethod NeoApa106Method; +typedef NeoAvrApa106Method NeoApa106Method; typedef NeoAvrWs2812xMethod Neo800KbpsMethod; typedef NeoAvr400KbpsMethod Neo400KbpsMethod; diff --git a/src/internal/methods/NeoPixelAvr.c b/src/internal/methods/NeoPixelAvr.c index bf602b9..e6572c6 100644 --- a/src/internal/methods/NeoPixelAvr.c +++ b/src/internal/methods/NeoPixelAvr.c @@ -466,7 +466,10 @@ void send_data_12mhz_800_PortB(uint8_t* data, size_t sizeData, uint8_t pinMask) [lo] "r" (lo)); } -void send_data_12mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask) +void send_data_12mhz_400(uint8_t* data, + size_t sizeData, + volatile uint8_t* port, + uint8_t pinMask) { volatile uint16_t i = (uint16_t)sizeData; // Loop counter volatile uint8_t* ptr = data; // Pointer to next byte @@ -477,7 +480,8 @@ void send_data_12mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL // ST instructions: ^ ^ ^ (T=0,6,15) - volatile uint8_t next, bit; + volatile uint8_t next; + volatile uint8_t bit; hi = *port | pinMask; lo = *port & ~pinMask; @@ -577,7 +581,10 @@ void send_data_16mhz_800(uint8_t* data, size_t sizeData, volatile uint8_t* port, [lo] "r" (lo)); } -void send_data_16mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, uint8_t pinMask) +void send_data_16mhz_400(uint8_t* data, + size_t sizeData, + volatile uint8_t* port, + uint8_t pinMask) { volatile size_t i = sizeData; // Loop counter volatile uint8_t* ptr = data; // Pointer to next byte @@ -590,7 +597,8 @@ void send_data_16mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL // ST instructions: ^ ^ ^ (T=0,8,20) - volatile uint8_t next, bit; + volatile uint8_t next; + volatile uint8_t bit; hi = *port | pinMask; lo = *port & ~pinMask; @@ -641,6 +649,68 @@ void send_data_16mhz_400(uint8_t* data, size_t sizeData, volatile uint8_t* port, [lo] "r" (lo)); } +// 0 400us (320-480) +// 1 1100us (960-1200) +// w 1600us +void send_data_16mhz_600(uint8_t* data, + size_t sizeData, + volatile uint8_t* port, + uint8_t pinMask) +{ + volatile size_t i = sizeData; // Loop counter + volatile uint8_t* ptr = data; // Pointer to next byte + volatile uint8_t b = *ptr++; // Current byte value + volatile uint8_t hi; // PORT w/output bit set high + volatile uint8_t lo; // PORT w/output bit set low + + // The 633 KHz clock on 16 MHz MCU. + // + // 25 inst. clocks per bit: HHHHHHHHxxxxxxxxxxLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,8,18) + + + volatile uint8_t next; + volatile uint8_t bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile( + "head40:" "\n\t" // Clk Pseudocode (T = 0) + "st %a[port], %[hi]" "\n\t" // 2 PORT = hi (T = 2) + "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0b10000000) + "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" "\n\t" // 2 nop nop (T = 6) + "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 8) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 9) + "rjmp .+0" "\n\t" // 2 nop nop (T = 11) + "rjmp .+0" "\n\t" // 2 nop nop (T = 13) + "rjmp .+0" "\n\t" // 2 nop nop (T = 15) + "dec %[bit]" "\n\t" // 1 bit-- (T = 16) + "breq nextbyte40" "\n\t" // 1-2 if(bit == 0) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 18) duplicate here improves high length for non byte boundary + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 21) + "nop" "\n\t" // 1 nop (T = 22) + "rjmp .+0" "\n\t" // 2 nop nop (T = 24) + "rjmp head40" "\n\t" // 2 -> head40 (next bit out) + "nextbyte40:" "\n\t" // (T = 18) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 20) duplicate here improves high length while reducing interbyte + "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 21) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 23) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 25) + "brne head40" "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e" (port), + [byte] "+r" (b), + [bit] "+r" (bit), + [next] "+r" (next), + [count] "+w" (i) + : [ptr] "e" (ptr), + [hi] "r" (hi), + [lo] "r" (lo)); +} + #elif (F_CPU >= 31000000UL) && (F_CPU <= 35000000UL) // 32Mhz CPU void send_data_32mhz(uint8_t* data,