diff --git a/COPYING b/COPYING index 7dcf8e8a..10926e87 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,3 @@ - GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 @@ -621,174 +620,56 @@ copy of the Program in return for a fee. END OF TERMS AND CONDITIONS + How to Apply These Terms to Your New Programs + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. -LGPL ADDENDUM: + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Copyright (C) + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. - 0. Additional Definitions. +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/NeoPixelBus.cpp b/NeoPixelBus.cpp deleted file mode 100644 index 1bba22f4..00000000 --- a/NeoPixelBus.cpp +++ /dev/null @@ -1,1172 +0,0 @@ -/*------------------------------------------------------------------------- -Arduino library to control a wide variety of WS2811- and WS2812-based RGB -LED devices such as Adafruit FLORA RGB Smart Pixels and NeoPixel strips. -Currently handles 400 and 800 KHz bitstreams on 8, 12 and 16 MHz ATmega -MCUs, with LEDs wired for RGB or GRB color order. 8 MHz MCUs provide -output on PORTB and PORTD, while 16 MHz chips can handle most output pins -(possible exception with upper PORT registers on the Arduino Mega). - -Originally written by Phil Burgess / Paint Your Dragon for Adafruit Industries, -contributions by PJRC and other members of the open source community. - -Adafruit invests time and resources providing this open source code, -please support Adafruit and open-source hardware by purchasing products -from Adafruit! - -------------------------------------------------------------------------- -NeoPixel is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -NeoPixel is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with NeoPixel. If not, see -. --------------------------------------------------------------------------*/ - -#include "NeoPixelBus.h" - -#if defined(ESP8266) -// due to linker overriding the ICACHE_RAM_ATTR for cpp files, these methods are -// moved into a C file so the attribute will be applied correctly -extern "C" void ICACHE_RAM_ATTR send_pixels_800(uint8_t* pixels, uint8_t* end, uint8_t pin); -extern "C" void ICACHE_RAM_ATTR send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin); -#endif - -NeoPixelBus::NeoPixelBus(uint16_t n, uint8_t p, uint8_t t) : - _countPixels(n), - _sizePixels(n * 3), - _pin(p), - _animationLastTick(0), - _activeAnimations(0), - _flagsPixels(t) -{ - setPin(p); - - _pixels = (uint8_t *)malloc(_sizePixels); - if (_pixels) - { - memset(_pixels, 0, _sizePixels); - } - - uint16_t animationSize = n * sizeof(FadeAnimation); - _animations = (FadeAnimation*)malloc(animationSize); - if (_animations) - { - memset(_animations, 0, animationSize); - } -} - -NeoPixelBus::~NeoPixelBus() -{ - if (_pixels) - free(_pixels); - if (_animations) - free(_animations); - - pinMode(_pin, INPUT); -} - -void NeoPixelBus::Begin(void) -{ - pinMode(_pin, OUTPUT); - digitalWrite(_pin, LOW); - - Dirty(); -} - -void NeoPixelBus::Show(void) -{ - if (!_pixels) - return; - if (!IsDirty()) - return; - - // 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 (!CanShow()) - { - delay(0); // allows for system yield if needed - } - // _endTime is a private member (rather than global var) so that mutliple - // instances on different pins can be quickly issued in succession (each - // instance doesn't delay the next). - - // In order to make this code runtime-configurable to work with any pin, - // SBI/CBI instructions are eschewed in favor of full PORT writes via the - // OUT or ST instructions. It relies on two facts: that peripheral - // functions (such as PWM) take precedence on output pins, so our PORT- - // wide writes won't interfere, and that interrupts are globally disabled - // while data is being issued to the LEDs, so no other code will be - // accessing the PORT. The code takes an initial 'snapshot' of the PORT - // state, computes 'pin high' and 'pin low' values, and writes these back - // to the PORT register as needed. - - noInterrupts(); // Need 100% focus on instruction timing - -#ifdef __AVR__ - - volatile uint16_t - i = _sizePixels; // Loop counter - volatile uint8_t - *ptr = _pixels, // Pointer to next byte - b = *ptr++, // Current byte value - hi, // PORT w/output bit set high - lo; // PORT w/output bit set low - - // Hand-tuned assembly code issues data to the LED drivers at a specific - // rate. There's separate code for different CPU speeds (8, 12, 16 MHz) - // for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The - // datastream timing for the LED drivers allows a little wiggle room each - // way (listed in the datasheets), so the conditions for compiling each - // case are set up for a range of frequencies rather than just the exact - // 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on - // devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based - // on the datasheet figures and have not been extensively tested outside - // the canonical 8/12/16 MHz speeds; there's no guarantee these will work - // close to the extremes (or possibly they could be pushed further). - // Keep in mind only one CPU speed case actually gets compiled; the - // resulting program isn't as massive as it might look from source here. - - // 8 MHz(ish) AVR --------------------------------------------------------- -#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { - // 800 KHz bitstream -#endif - - volatile uint8_t n1, n2 = 0; // First, next bits out - - // Squeezing an 800 KHz stream out of an 8 MHz chip requires code - // specific to each PORT register. At present this is only written - // to work with pins on PORTD or PORTB, the most likely use case -- - // this covers all the pins on the Adafruit Flora and the bulk of - // digital pins on the Arduino Pro 8 MHz (keep in mind, this code - // doesn't even get compiled for 16 MHz boards like the Uno, Mega, - // Leonardo, etc., so don't bother extending this out of hand). - // Additional PORTs could be added if you really need them, just - // duplicate the else and loop and change the PORT. Each add'l - // PORT will require about 150(ish) bytes of program space. - - // 10 instruction clocks per bit: HHxxxxxLLL - // OUT instructions: ^ ^ ^ (T=0,2,7) - -#ifdef PORTD // PORTD isn't present on ATtiny85, etc. - - if (_port == &PORTD) - { - - hi = PORTD | _pinMask; - lo = PORTD & ~_pinMask; - n1 = lo; - if(b & 0x80) n1 = hi; - - // Dirty trick: RJMPs proceeding to the next instruction are used - // to delay two clock cycles in one instruction word (rather than - // using two NOPs). This was necessary in order to squeeze the - // loop down to exactly 64 words -- the maximum possible for a - // relative branch. - - asm volatile( - "headD:" "\n\t" // Clk Pseudocode - // Bit 7: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[_port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 6" "\n\t" // 1-2 if(b & 0x40) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 6: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[_port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 5" "\n\t" // 1-2 if(b & 0x20) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 5: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[_port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 4" "\n\t" // 1-2 if(b & 0x10) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 4: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[_port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 3" "\n\t" // 1-2 if(b & 0x08) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 3: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[_port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 2" "\n\t" // 1-2 if(b & 0x04) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 2: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[_port] , %[n2]" "\n\t" // 1 PORT = n2 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 1" "\n\t" // 1-2 if(b & 0x02) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "rjmp .+0" "\n\t" // 2 nop nop - // Bit 1: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo - "out %[_port] , %[n1]" "\n\t" // 1 PORT = n1 - "rjmp .+0" "\n\t" // 2 nop nop - "sbrc %[byte] , 0" "\n\t" // 1-2 if(b & 0x01) - "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "sbiw %[count], 1" "\n\t" // 2 i-- (don't act on Z flag yet) - // Bit 0: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi - "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo - "out %[_port] , %[n2]" "\n\t" // 1 PORT = n2 - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) - "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo - "brne headD" "\n" // 2 while(i) (Z flag set above) - : [byte] "+r" (b), - [n1] "+r" (n1), - [n2] "+r" (n2), - [count] "+w" (i) - : [_port] "I" (_SFR_IO_ADDR(PORTD)), - [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); - - } - else if (_port == &PORTB) - { - -#endif // PORTD - - // Same as above, just switched to PORTB and stripped of comments. - hi = PORTB | _pinMask; - lo = PORTB & ~_pinMask; - n1 = lo; - if(b & 0x80) n1 = hi; - - asm volatile( - "headB:" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[_port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 6" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[_port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 5" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[_port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 4" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[_port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 3" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[_port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 2" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[_port] , %[n2]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 1" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "rjmp .+0" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n2] , %[lo]" "\n\t" - "out %[_port] , %[n1]" "\n\t" - "rjmp .+0" "\n\t" - "sbrc %[byte] , 0" "\n\t" - "mov %[n2] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "mov %[n1] , %[lo]" "\n\t" - "out %[_port] , %[n2]" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[n1] , %[hi]" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "brne headB" "\n" - : [byte] "+r" (b), [n1] "+r" (n1), [n2] "+r" (n2), [count] "+w" (i) - : [_port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - -#ifdef PORTD - } // endif PORTB -#endif - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // end 800 KHz, do 400 KHz - - // Timing is more relaxed; unrolling the inner loop for each bit is - // not necessary. Still using the peculiar RJMPs as 2X NOPs, not out - // of need but just to trim the code size down a little. - // This 400-KHz-datastream-on-8-MHz-CPU code is not quite identical - // to the 800-on-16 code later -- the hi/lo timing between WS2811 and - // WS2812 is not simply a 2:1 scale! - - // 20 inst. clocks per bit: HHHHxxxxxxLLLLLLLLLL - // ST instructions: ^ ^ ^ (T=0,4,10) - - volatile uint8_t next, bit; - - hi = *_port | _pinMask; - lo = *_port & ~_pinMask; - next = lo; - bit = 8; - - asm volatile( - "head20:" "\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 & 128) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "st %a[_port], %[next]" "\n\t" // 2 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "dec %[bit]" "\n\t" // 1 bit-- (T = 8) - "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "rjmp .+0" "\n\t" // 2 nop nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out) - "nextbyte20:" "\n\t" // (T = 10) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) - "nop" "\n\t" // 1 nop (T = 13) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 14) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 16) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne head20" "\n" // 2 if(i != 0) -> (next byte) - : [_port] "+e" (_port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [hi] "r" (hi), - [lo] "r" (lo), - [ptr] "e" (ptr)); - } -#endif - - // 12 MHz(ish) AVR -------------------------------------------------------- -#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { - // 800 KHz bitstream -#endif - - // In the 12 MHz case, an optimized 800 KHz datastream (no dead time - // between bytes) requires a PORT-specific loop similar to the 8 MHz - // code (but a little more relaxed in this case). - - // 15 instruction clocks per bit: HHHHxxxxxxLLLLL - // OUT instructions: ^ ^ ^ (T=0,4,10) - - volatile uint8_t next; - -#ifdef PORTD - - if (_port == &PORTD) - { - - hi = PORTD | _pinMask; - lo = PORTD & ~_pinMask; - next = lo; - if(b & 0x80) next = hi; - - // Don't "optimize" the OUT calls into the bitTime subroutine; - // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs! - asm volatile( - "headD:" "\n\t" // (T = 0) - "out %[_port], %[hi]" "\n\t" // (T = 1) - "rcall bitTimeD" "\n\t" // Bit 7 (T = 15) - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 6 - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 5 - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 4 - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 3 - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 2 - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeD" "\n\t" // Bit 1 - // Bit 0: - "out %[_port] , %[hi]" "\n\t" // 1 PORT = hi (T = 1) - "rjmp .+0" "\n\t" // 2 nop nop (T = 3) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) - "out %[_port] , %[next]" "\n\t" // 1 PORT = next (T = 6) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) - "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) - "out %[_port] , %[lo]" "\n\t" // 1 PORT = lo (T = 11) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 13) - "brne headD" "\n\t" // 2 if(i != 0) -> (next byte) - "rjmp doneD" "\n\t" - "bitTimeD:" "\n\t" // nop nop nop (T = 4) - "out %[_port], %[next]" "\n\t" // 1 PORT = next (T = 5) - "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) - "sbrc %[byte], 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) - "nop" "\n\t" // 1 (T = 10) - "out %[_port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) - "ret" "\n\t" // 4 nop nop nop nop (T = 15) - "doneD:" "\n" - : [byte] "+r" (b), - [next] "+r" (next), - [count] "+w" (i) - : [_port] "I" (_SFR_IO_ADDR(PORTD)), - [ptr] "e" (ptr), - [hi] "r" (hi), - [lo] "r" (lo)); - - } - else if (_port == &PORTB) - { - -#endif // PORTD - - hi = PORTB | _pinMask; - lo = PORTB & ~_pinMask; - next = lo; - if(b & 0x80) next = hi; - - // Same as above, just set for PORTB & stripped of comments - asm volatile( - "headB:" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port], %[hi]" "\n\t" - "rcall bitTimeB" "\n\t" - "out %[_port] , %[hi]" "\n\t" - "rjmp .+0" "\n\t" - "ld %[byte] , %a[ptr]+" "\n\t" - "out %[_port] , %[next]" "\n\t" - "mov %[next] , %[lo]" "\n\t" - "sbrc %[byte] , 7" "\n\t" - "mov %[next] , %[hi]" "\n\t" - "nop" "\n\t" - "out %[_port] , %[lo]" "\n\t" - "sbiw %[count], 1" "\n\t" - "brne headB" "\n\t" - "rjmp doneB" "\n\t" - "bitTimeB:" "\n\t" - "out %[_port], %[next]" "\n\t" - "mov %[next], %[lo]" "\n\t" - "rol %[byte]" "\n\t" - "sbrc %[byte], 7" "\n\t" - "mov %[next], %[hi]" "\n\t" - "nop" "\n\t" - "out %[_port], %[lo]" "\n\t" - "ret" "\n\t" - "doneB:" "\n" - : [byte] "+r" (b), [next] "+r" (next), [count] "+w" (i) - : [_port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), - [lo] "r" (lo)); - -#ifdef PORTD - } -#endif - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // 400 KHz - - // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL - // ST instructions: ^ ^ ^ (T=0,6,15) - - volatile uint8_t next, bit; - - hi = *_port | _pinMask; - lo = *_port & ~_pinMask; - next = lo; - bit = 8; - - asm volatile( - "head30:" "\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 & 128) - "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) - "rjmp .+0" "\n\t" // 2 nop nop (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "nop" "\n\t" // 1 nop (T = 15) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 17) - "rjmp .+0" "\n\t" // 2 nop nop (T = 19) - "dec %[bit]" "\n\t" // 1 bit-- (T = 20) - "breq nextbyte30" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 22) - "rjmp .+0" "\n\t" // 2 nop nop (T = 24) - "rjmp .+0" "\n\t" // 2 nop nop (T = 26) - "rjmp .+0" "\n\t" // 2 nop nop (T = 28) - "rjmp head30" "\n\t" // 2 -> head30 (next bit out) - "nextbyte30:" "\n\t" // (T = 22) - "nop" "\n\t" // 1 nop (T = 23) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 24) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 26) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 28) - "brne head30" "\n" // 1-2 if(i != 0) -> (next byte) - : [_port] "+e" (_port), - [byte] "+r" (b), - [bit] "+r" (bit), - [next] "+r" (next), - [count] "+w" (i) - : [hi] "r" (hi), - [lo] "r" (lo), - [ptr] "e" (ptr)); - } -#endif - - // 16 MHz(ish) AVR -------------------------------------------------------- -#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { - // 800 KHz bitstream -#endif - - // WS2811 and WS2812 have different hi/lo duty cycles; this is - // similar but NOT an exact copy of the prior 400-on-8 code. - - // 20 inst. clocks per bit: HHHHHxxxxxxxxLLLLLLL - // ST instructions: ^ ^ ^ (T=0,5,13) - - volatile uint8_t next, bit; - - hi = *_port | _pinMask; - lo = *_port & ~_pinMask; - next = lo; - bit = 8; - - asm volatile( - "head20:" "\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 & 128) - "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "dec %[bit]" "\n\t" // 1 bit-- (T = 5) - "st %a[_port], %[next]" "\n\t" // 2 PORT = next (T = 7) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 8) - "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) (from dec above) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "nop" "\n\t" // 1 nop (T = 13) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) - "nop" "\n\t" // 1 nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp head20" "\n\t" // 2 -> head20 (next bit out) - "nextbyte20:" "\n\t" // (T = 10) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 11) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 13) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) - "nop" "\n\t" // 1 nop (T = 16) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) - "brne head20" "\n" // 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)); - - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // 400 KHz - - // The 400 KHz clock on 16 MHz MCU is the most 'relaxed' version. - - // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL - // ST instructions: ^ ^ ^ (T=0,8,20) - - volatile uint8_t next, 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 & 128) - "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 4) - "rjmp .+0" "\n\t" // 2 nop nop (T = 6) - "rjmp .+0" "\n\t" // 2 nop nop (T = 8) - "st %a[_port], %[next]" "\n\t" // 2 PORT = next (T = 10) - "rjmp .+0" "\n\t" // 2 nop nop (T = 12) - "rjmp .+0" "\n\t" // 2 nop nop (T = 14) - "rjmp .+0" "\n\t" // 2 nop nop (T = 16) - "rjmp .+0" "\n\t" // 2 nop nop (T = 18) - "rjmp .+0" "\n\t" // 2 nop nop (T = 20) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 22) - "nop" "\n\t" // 1 nop (T = 23) - "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 24) - "dec %[bit]" "\n\t" // 1 bit-- (T = 25) - "breq nextbyte40" "\n\t" // 1-2 if(bit == 0) - "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 27) - "nop" "\n\t" // 1 nop (T = 28) - "rjmp .+0" "\n\t" // 2 nop nop (T = 30) - "rjmp .+0" "\n\t" // 2 nop nop (T = 32) - "rjmp .+0" "\n\t" // 2 nop nop (T = 34) - "rjmp .+0" "\n\t" // 2 nop nop (T = 36) - "rjmp .+0" "\n\t" // 2 nop nop (T = 38) - "rjmp head40" "\n\t" // 2 -> head40 (next bit out) - "nextbyte40:" "\n\t" // (T = 27) - "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 28) - "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 30) - "rjmp .+0" "\n\t" // 2 nop nop (T = 32) - "st %a[_port], %[lo]" "\n\t" // 2 PORT = lo (T = 34) - "rjmp .+0" "\n\t" // 2 nop nop (T = 36) - "sbiw %[count], 1" "\n\t" // 2 i-- (T = 38) - "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)); - } -#endif - -#else -#error "CPU SPEED NOT SUPPORTED" -#endif - -#elif defined(ESP8266) - - uint8_t* p = _pixels; - uint8_t* end = p + _sizePixels; - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - - - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { -#endif - // 800 KHz bitstream - send_pixels_800(p, end, _pin); - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // 400 kHz bitstream - send_pixels_400(p, end, _pin); - } -#endif - -#elif defined(__arm__) - - -#if defined(__MK20DX128__) || defined(__MK20DX256__) // Teensy 3.0 & 3.1 -#define CYCLES_800_T0H (F_CPU / 4000000) // 0.4us -#define CYCLES_800_T1H (F_CPU / 1250000) // 0.8us -#define CYCLES_800 (F_CPU / 800000) // 1.25us per bit -#define CYCLES_400_T0H (F_CPU / 2000000) -#define CYCLES_400_T1H (F_CPU / 833333) -#define CYCLES_400 (F_CPU / 400000) - - uint8_t *p = _pixels, - *end = p + _sizePixels, pix, mask; - volatile uint8_t *set = portSetRegister(_pin), - *clr = portClearRegister(_pin); - uint32_t cyc; - - ARM_DEMCR |= ARM_DEMCR_TRCENA; - ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { -#endif - // 800 KHz bitstream - cyc = ARM_DWT_CYCCNT + CYCLES_800; - while (p < end) - { - pix = *p++; - for (mask = 0x80; mask; mask >>= 1) - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_800); - cyc = ARM_DWT_CYCCNT; - *set = 1; - if (pix & mask) - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T1H); - } - else - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_800_T0H); - } - *clr = 1; - } - } - while (ARM_DWT_CYCCNT - cyc < CYCLES_800); -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // 400 kHz bitstream - cyc = ARM_DWT_CYCCNT + CYCLES_400; - while (p < end) - { - pix = *p++; - for(mask = 0x80; mask; mask >>= 1) - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_400); - cyc = ARM_DWT_CYCCNT; - *set = 1; - if (pix & mask) - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T1H); - } - else - { - while (ARM_DWT_CYCCNT - cyc < CYCLES_400_T0H); - } - *clr = 1; - } - } - while (ARM_DWT_CYCCNT - cyc < CYCLES_400); - } -#endif - -#elif defined(__MKL26Z64__) // Teensy-LC - -#if F_CPU == 48000000 - uint8_t *p = pixels, - pix, count, dly, - bitmask = digitalPinToBitMask(pin); - volatile uint8_t *reg = portSetRegister(pin); - uint32_t num = numBytes; - asm volatile( - "L%=_begin:" "\n\t" - "ldrb %[pix], [%[p], #0]" "\n\t" - "lsl %[pix], #24" "\n\t" - "movs %[count], #7" "\n\t" - "L%=_loop:" "\n\t" - "lsl %[pix], #1" "\n\t" - "bcs L%=_loop_one" "\n\t" - "L%=_loop_zero:" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_loop_delay_T0H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T0H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_loop_delay_T0L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T0L" "\n\t" - "b L%=_next" "\n\t" - "L%=_loop_one:" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_loop_delay_T1H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T1H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_loop_delay_T1L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_loop_delay_T1L" "\n\t" - "nop" "\n\t" - "L%=_next:" "\n\t" - "sub %[count], #1" "\n\t" - "bne L%=_loop" "\n\t" - "lsl %[pix], #1" "\n\t" - "bcs L%=_last_one" "\n\t" - "L%=_last_zero:" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #4" "\n\t" - "L%=_last_delay_T0H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T0H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #10" "\n\t" - "L%=_last_delay_T0L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T0L" "\n\t" - "b L%=_repeat" "\n\t" - "L%=_last_one:" - "strb %[bitmask], [%[reg], #0]" "\n\t" - "movs %[dly], #13" "\n\t" - "L%=_last_delay_T1H:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T1H" "\n\t" - "strb %[bitmask], [%[reg], #4]" "\n\t" - "movs %[dly], #1" "\n\t" - "L%=_last_delay_T1L:" "\n\t" - "sub %[dly], #1" "\n\t" - "bne L%=_last_delay_T1L" "\n\t" - "nop" "\n\t" - "L%=_repeat:" "\n\t" - "add %[p], #1" "\n\t" - "sub %[num], #1" "\n\t" - "bne L%=_begin" "\n\t" - "L%=_done:" "\n\t" - : [p] "+r" (p), - [pix] "=&r" (pix), - [count] "=&r" (count), - [dly] "=&r" (dly), - [num] "+r" (num) - : [bitmask] "r" (bitmask), - [reg] "r" (reg) - ); -#else -#error "Sorry, only 48 MHz is supported, please set Tools > CPU Speed to 48 MHz" -#endif - -#else // Arduino Due - -#define SCALE VARIANT_MCK / 2UL / 1000000UL -#define INST (2UL * F_CPU / VARIANT_MCK) -#define TIME_800_0 ((int)(0.40 * SCALE + 0.5) - (5 * INST)) -#define TIME_800_1 ((int)(0.80 * SCALE + 0.5) - (5 * INST)) -#define PERIOD_800 ((int)(1.25 * SCALE + 0.5) - (5 * INST)) -#define TIME_400_0 ((int)(0.50 * SCALE + 0.5) - (5 * INST)) -#define TIME_400_1 ((int)(1.20 * SCALE + 0.5) - (5 * INST)) -#define PERIOD_400 ((int)(2.50 * SCALE + 0.5) - (5 * INST)) - - int pinMask, time0, time1, period, t; - Pio *port; - volatile WoReg *portSet, *portClear, *timeValue, *timeReset; - uint8_t *p, *end, pix, mask; - - pmc_set_writeprotect(false); - pmc_enable_periph_clk((uint32_t)TC3_IRQn); - TC_Configure(TC1, 0, - TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1); - TC_Start(TC1, 0); - - pinMask = g_APinDescription[_pin].ulPin; // Don't 'optimize' these into - port = g_APinDescription[_pin].pPort; // declarations above. Want to - portSet = &(port->PIO_SODR); // burn a few cycles after - portClear = &(port->PIO_CODR); // starting timer to minimize - timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'. - timeReset = &(TC1->TC_CHANNEL[0].TC_CCR); - p = _pixels; - end = p + _sizePixels; - pix = *p++; - mask = 0x80; - -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - if ((_flagsPixels & NEO_SPDMASK) == NEO_KHZ800) - { -#endif - // 800 KHz bitstream - time0 = TIME_800_0; - time1 = TIME_800_1; - period = PERIOD_800; -#ifdef INCLUDE_NEO_KHZ400_SUPPORT - } - else - { - // 400 KHz bitstream - time0 = TIME_400_0; - time1 = TIME_400_1; - period = PERIOD_400; - } -#endif - - for (t = time0;; t = time0) - { - if (pix & mask) - t = time1; - while (*timeValue < period); - *portSet = pinMask; - *timeReset = TC_CCR_CLKEN | TC_CCR_SWTRG; - while (*timeValue < t); - *portClear = pinMask; - if (!(mask >>= 1)) - { // This 'inside-out' loop logic utilizes - if (p >= end) - break; // idle time to minimize inter-byte delays. - pix = *p++; - mask = 0x80; - } - } - while (*timeValue < period); // Wait for last bit - TC_Stop(TC1, 0); - -#endif // end Arduino Due - -#endif // end Architecture select - - interrupts(); - ResetDirty(); - _endTime = micros(); // Save EOD time for latch on next call -} - - -// Set the output pin number -void NeoPixelBus::setPin(uint8_t p) -{ - pinMode(_pin, INPUT); - _pin = p; - pinMode(p, OUTPUT); - digitalWrite(p, LOW); -#ifdef __AVR__ - _port = portOutputRegister(digitalPinToPort(p)); - _pinMask = digitalPinToBitMask(p); -#endif -} - -// Set pixel color from separate R,G,B components: -void NeoPixelBus::SetPixelColor( - uint16_t n, - uint8_t r, - uint8_t g, - uint8_t b) -{ - if (n < _countPixels) - { - // clear any animation - if (_animations[n].time != 0) - { - _activeAnimations--; - _animations[n].time = 0; - _animations[n].remaining = 0; - } - UpdatePixelColor(n, r, g, b); - } -} - -void NeoPixelBus::ClearTo(uint8_t r, uint8_t g, uint8_t b) -{ - for (uint8_t n = 0; n < _countPixels; n++) - { - SetPixelColor(n, r, g, b); - } -} - -// Set pixel color from separate R,G,B components: -void NeoPixelBus::UpdatePixelColor( - uint16_t n, - uint8_t r, - uint8_t g, - uint8_t b) -{ - Dirty(); - - uint8_t *p = &_pixels[n * 3]; - - uint8_t colorOrder = (_flagsPixels & NEO_COLMASK); - if (colorOrder == NEO_GRB) - { - *p++ = g; - *p++ = r; - *p = b; - } - else if (colorOrder == NEO_RGB) - { - *p++ = r; - *p++ = g; - *p = b; - } - else - { - *p++ = b; - *p++ = r; - *p = g; - } -} - -// Query color from previously-set pixel (returns packed 32-bit RGB value) -RgbColor NeoPixelBus::GetPixelColor(uint16_t n) const -{ - if (n < _countPixels) - { - RgbColor c; - uint8_t *p = &_pixels[n * 3]; - - uint8_t colorOrder = (_flagsPixels & NEO_COLMASK); - if (colorOrder == NEO_GRB) - { - c.G = *p++; - c.R = *p++; - c.B = *p; - } - else if (colorOrder == NEO_RGB) - { - c.R = *p++; - c.G = *p++; - c.B = *p; - } - else - { - c.B = *p++; - c.R = *p++; - c.G = *p; - } - - return c; - } - - return RgbColor(0); // Pixel # is out of bounds -} - -void NeoPixelBus::LinearFadePixelColor(uint16_t time, uint16_t n, RgbColor color) -{ - if (n >= _countPixels) - { - return; - } - - if (_animations[n].time != 0) - { - _activeAnimations--; - } - - _animations[n].time = time; - _animations[n].remaining = time; - _animations[n].target = color; - _animations[n].origin = GetPixelColor(n); - - if (time > 0) - { - _activeAnimations++; - } - else - { - SetPixelColor(n, _animations[n].target); - } -} - -void NeoPixelBus::FadeTo(uint16_t time, RgbColor color) -{ - for (uint8_t n = 0; n < _countPixels; n++) - { - LinearFadePixelColor(time, n, color); - } -} - -void NeoPixelBus::StartAnimating() -{ - _animationLastTick = millis(); -} - -void NeoPixelBus::UpdateAnimations() -{ - uint32_t currentTick = millis(); - - if (_animationLastTick != 0) - { - uint32_t delta = currentTick - _animationLastTick; - if (delta > 0) - { - uint16_t countAnimations = _activeAnimations; - - FadeAnimation* pAnim; - RgbColor color; - - for (uint16_t iAnim = 0; iAnim < _countPixels && countAnimations > 0; iAnim++) - { - pAnim = &_animations[iAnim]; - - if (pAnim->remaining > delta) - { - pAnim->remaining -= delta; - - uint8_t progress = (pAnim->time - pAnim->remaining) * (uint32_t)256 / pAnim->time; - - color = RgbColor::LinearBlend(pAnim->origin, - pAnim->target, - progress); - - UpdatePixelColor(iAnim, color); - countAnimations--; - } - else if (pAnim->remaining > 0) - { - // specifically calling SetPixelColor so it will clear animation state - SetPixelColor(iAnim, pAnim->target); - countAnimations--; - } - } - } - } - - _animationLastTick = currentTick; -} - diff --git a/NeoPixelBus.h b/NeoPixelBus.h deleted file mode 100644 index bbb778a7..00000000 --- a/NeoPixelBus.h +++ /dev/null @@ -1,137 +0,0 @@ -/*-------------------------------------------------------------------- -This file is a modification of the Adafruit NeoPixel library. - -NeoPixel is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -NeoPixel is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with NeoPixel. If not, see -. ---------------------------------------------------------------------*/ -#pragma once - -#include -#include "RgbColor.h" - -// '_flagsPixels' flags for LED _pixels (third parameter to constructor): -#define NEO_RGB 0x00 // Wired for RGB data order -#define NEO_GRB 0x01 // Wired for GRB data order -#define NEO_BRG 0x04 -#define NEO_COLMASK 0x05 - -#define NEO_KHZ400 0x00 // 400 KHz datastream -#define NEO_KHZ800 0x02 // 800 KHz datastream -#define NEO_SPDMASK 0x02 -#define NEO_DIRTY 0x80 // a change was made it _pixels that requires a show - -// v1 NeoPixels aren't handled by default, include the following define before the -// NeoPixelBus library include to support the slower bus speeds -//#define INCLUDE_NEO_KHZ400_SUPPORT - -class NeoPixelBus -{ -public: - // Constructor: number of LEDs, pin number, LED type - NeoPixelBus(uint16_t n, uint8_t p, uint8_t t = NEO_GRB | NEO_KHZ800); - ~NeoPixelBus(); - - inline uint16_t getPixelCount() - { - return _countPixels; - } - - void Begin(); - void Show(); - inline bool CanShow(void) - { - return (micros() - _endTime) >= 50L; - } - void ClearTo(uint8_t r, uint8_t g, uint8_t b); - void ClearTo(RgbColor c) - { - ClearTo(c.R, c.G, c.B); - } - - bool IsDirty() - { - return (_flagsPixels & NEO_DIRTY); - }; - void Dirty() - { - _flagsPixels |= NEO_DIRTY; - }; - void ResetDirty() - { - _flagsPixels &= ~NEO_DIRTY; - } - - uint8_t* Pixels() const - { - return _pixels; - }; - uint16_t PixelCount() const - { - return _countPixels; - }; - - void SetPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); - void SetPixelColor(uint16_t n, RgbColor c) - { - SetPixelColor(n, c.R, c.G, c.B); - }; - - RgbColor GetPixelColor(uint16_t n) const; - - void StartAnimating(); - void UpdateAnimations(); - - bool IsAnimating() const - { - return _activeAnimations > 0; - } - void LinearFadePixelColor(uint16_t time, uint16_t n, RgbColor color); - - void FadeTo(uint16_t time, RgbColor color); - -private: - void setPin(uint8_t p); - void UpdatePixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b); - void UpdatePixelColor(uint16_t n, RgbColor c) - { - UpdatePixelColor(n, c.R, c.G, c.B); - }; - - const uint16_t _countPixels; // Number of RGB LEDs in strip - const uint16_t _sizePixels; // Size of '_pixels' buffer below - - uint8_t _flagsPixels; // Pixel flags (400 vs 800 KHz, RGB vs GRB color) - uint8_t _pin; // Output pin number - uint8_t* _pixels; // Holds LED color values (3 bytes each) - uint32_t _endTime; // Latch timing reference -#ifdef __AVR__ - const volatile uint8_t* _port; // Output PORT register - uint8_t _pinMask; // Output PORT bitmask -#endif - - struct FadeAnimation - { - uint16_t time; - uint16_t remaining; - - RgbColor target; - RgbColor origin; - }; - - uint16_t _activeAnimations; - FadeAnimation* _animations; - uint32_t _animationLastTick; - -}; - diff --git a/ReadMe.md b/ReadMe.md index a61bf423..fdaae9a1 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -4,86 +4,33 @@ Arduino NeoPixel library -ESP8266 CUSTOMERS PLEASE READ: While this branch does work with the esp8266, due to the latest SDK releases it will not function reliably when WiFi is being used. Therefore I suggest you use the DmaDriven or UartDriven branches, which both include solutions that will work with WiFi on. Further they contains enhancements that just can't be supported on AVR platform. Including HslColor object and an enhanced animator manager. +A library to control one wire protocol RGB and RGBW leds like SK6812, WS2811, and WS2812 that are commonly refered to as NeoPixels. +Supports most Arduino platforms. +This is the most funtional library for the Esp8266 as it provides solutions for all Esp8266 module types even when WiFi is used. -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Makuna/NeoPixelBus?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -Clone this into your Arduino\Library folder - -This library is a modification of the Adafruit NeoPixel library. -The Api is similiar, but it removes the overal brightness feature and adds animation support. - -## Installing This Library -Create a directory in your Arduino\Library folder named "NeoPixelBus" -Clone (Git) this project into that folder. -It should now show up in the import list. - -## Samples -### NeoPixelTest -this is simple example that sets four neopixels to red, green, blue, and then white in order; and then flashes them. If the first pixel is green and the second is red, you need to pass the NEO_RGB flag into the NeoPixelBus constructor. -### NeoPixelFun -this is a more complex example, that includes code for three effects, and demonstrates animations. - -## API Documentation - -### RgbColor object -This represents a color and exposes useful methods to manipulate colors. - -#### RgbColor(uint8_t r, uint8_t g, uint8_t b) -instantiates a RgbColor object with the given r, g, b values. - -#### RgbColor(uint8_t brightness) -instantiates a RgbColor object with the given brightness. 0 is black, 128 is grey, 255 is white. +Please read this best practices link before connecting your NeoPixels, it will save you alot of time and effort. +[Adafruit NeoPixel Best Practices](https://learn.adafruit.com/adafruit-neopixel-uberguide/best-practices) -#### uint8_t CalculateBrightness() -returns the general brightness of the pixe, averaging color. - -#### void Darken(uint8_t delta) -this will darken the color by the given amount, blending toward black. This method is destructive in that you can't expect to then call lighten and return to the original color. - -#### void Lighten(uint8_t delta) -this will lighten the color by the given amount, blending toward white. This method is destructive in that you can't expect to then call darken and return to the original color. - -#### static RgbColor LinearBlend(RgbColor left, RgbColor right, uint8_t progress) -this will return a color that is a blend between the given colors. The amount to blend is given by the value of progress, 0 will return the left value, 255 will return the right value, 128 will return the value between them. - -NOTE: This is not an accurate "visible light" color blend but is fast and in most cases good enough. - -### NeoPixelBus object -This represents a single NeoPixel Bus that is connected by a single pin. Please see Adafruit's documentation for details, but the differences are documented below. - -#### NeoPixelBus(uint16_t n, uint8_t p = 6, uint8_t t = NEO_GRB | NEO_KHZ800); -instantiates a NewoPixelBus object, with n number of pixels on the bus, over the p pin, using the defined NeoPixel type. -There are some NeoPixels that address the color values differently, so if you set the green color but it displays as red, use the NEO_RGB type flag. - -``` -NeoPixelBus strip = NeoPixelBus(4, 8, NEO_RGB | NEO_KHZ800); -``` -It is rare, but some older NeoPixels require a slower communications speed, to include this support you must include the following define before the NeoPixelBus library include and then include the NEO_KHZ400 type flag to enable this slower speed. +For quick questions jump on Gitter and ask away. +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Makuna/NeoPixelBus?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -``` -#define INCLUDE_NEO_KHZ400_SUPPORT -#include +For bugs, make sure there isn't an active issue and then create one. -NeoPixelBus strip = NeoPixelBus(4, 8, NEO_RGB | NEO_KHZ400); -``` +This new library supports a templatized model of defining which method gets used to send data and what order and size the pixel data is sent in. This new design creates the smallest code for each definition of features used. Further it allows for picking which method to send the data on the Esp8266 in an easy to change way. +Please see examples to become familiar with the new design. +Due to this design you will often realize over 500 bytes of more program storage for your sketch. Important for the smallest Arduinos project. -#### void SetPixelColor(uint16_t n, RgbColor c) -This allows setting a pixel on the bus to a color as defined by a color object. If an animation is actively running on a pixel, it will be stopped. -#### RgbColor GetPixelColor(uint16_t n) const -this allows retrieving the current pixel color +## Installing This Library (prefered) +Open the Library Manager and search for "NeoPixelBus by Makuna" and install -#### void LinearFadePixelColor(uint16_t time, uint16_t n, RgbColor color) -this will setup an animation for a pixel to linear fade between the current color and the given color over the time given. The time is in milliseconds. +## Installing This Library From GitHub +Create a directory in your Arduino\Library folder named "NeoPixelBus" +Clone (Git) this project into that folder. +It should now show up in the import list when you restart Arduino IDE. -#### void StartAnimating() -this method will initialize the animation state. This should be called only if there are no active animations and new animations are started. +## Documentation +[See Wiki](https://github.com/Makuna/NeoPixelBus/wiki) -#### void UpdateAnimations() -this method will allow the animations to be processed and update the pixel color state. -NOTE: Show must still be called to push the color state to the physical NeoPixels. -#### bool IsAnimating() const -this method will return the current animation state. It will return false if there are no active animations. diff --git a/RgbColor.cpp b/RgbColor.cpp deleted file mode 100644 index ecaa7685..00000000 --- a/RgbColor.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/*-------------------------------------------------------------------- -NeoPixel is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as -published by the Free Software Foundation, either version 3 of -the License, or (at your option) any later version. - -NeoPixel is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with NeoPixel. If not, see -. ---------------------------------------------------------------------*/ - -#include "RgbColor.h" - -uint8_t RgbColor::CalculateBrightness() -{ - return (uint8_t)(((uint16_t)R + (uint16_t)G + (uint16_t)B) / 3); -} - -void RgbColor::Darken(uint8_t delta) -{ - if (R > delta) - { - R -= delta; - } - else - { - R = 0; - } - - if (G > delta) - { - G -= delta; - } - else - { - G = 0; - } - - if (B > delta) - { - B -= delta; - } - else - { - B = 0; - } -} - -void RgbColor::Lighten(uint8_t delta) -{ - if (R < 255 - delta) - { - R += delta; - } - else - { - R = 255; - } - - if (G < 255 - delta) - { - G += delta; - } - else - { - G = 255; - } - - if (B < 255 - delta) - { - B += delta; - } - else - { - B = 255; - } -} - -RgbColor RgbColor::LinearBlend(RgbColor left, RgbColor right, uint8_t progress) -{ - return RgbColor( left.R + ((right.R - left.R) * progress / 255), - left.G + ((right.G - left.G) * progress / 255), - left.B + ((right.B - left.B) * progress / 255)); -} \ No newline at end of file diff --git a/examples/NeoPixelAnimation/NeoPixelAnimation.ino b/examples/NeoPixelAnimation/NeoPixelAnimation.ino new file mode 100644 index 00000000..289a8452 --- /dev/null +++ b/examples/NeoPixelAnimation/NeoPixelAnimation.ino @@ -0,0 +1,226 @@ +// NeoPixelAnimation +// This example will randomly pick a new color for each pixel and animate +// the current color to the new color over a random small amount of time, using +// a randomly selected animation curve. +// It will repeat this process once all pixels have finished the animation +// +// This will demonstrate the use of the NeoPixelAnimator extended time feature. +// This feature allows for different time scales to be used, allowing slow extended +// animations to be created. +// +// This will demonstrate the use of the NeoEase animation ease methods; that provide +// simulated acceleration to the animations. +// +// It also includes platform specific code for Esp8266 that demonstrates easy +// animation state and function definition inline. This is not available on AVR +// Arduinos; but the AVR compatible code is also included for comparison. +// +// The example includes some serial output that you can follow along with as it +// does the animation. +// + +#include +#include + +const uint16_t PixelCount = 4; // make sure to set this to the number of pixels in your strip +const uint8_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266 + +NeoPixelBus strip(PixelCount, PixelPin); +// For Esp8266, the Pin is ignored and it uses GPIO3. +// There are other Esp8266 alternative methods that provide more pin options, but also have +// other side effects. +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod also ignores the pin parameter and uses GPI02 +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod will work with all but pin 16, but is not stable with WiFi +// being active + +// NeoPixel animation time management object +NeoPixelAnimator animations(PixelCount, NEO_CENTISECONDS); + +// create with enough animations to have one per pixel, depending on the animation +// effect, you may need more or less. +// +// since the normal animation time range is only about 65 seconds, by passing timescale value +// to the NeoPixelAnimator constructor we can increase the time range, but we also increase +// the time between the animation updates. +// NEO_CENTISECONDS will update the animations every 100th of a second rather than the default +// of a 1000th of a second, but the time range will now extend from about 65 seconds to about +// 10.9 minutes. But you must remember that the values passed to StartAnimations are now +// in centiseconds. +// +// Possible values from 1 to 32768, and there some helpful constants defined as... +// NEO_MILLISECONDS 1 // ~65 seconds max duration, ms updates +// NEO_CENTISECONDS 10 // ~10.9 minutes max duration, centisecond updates +// NEO_DECISECONDS 100 // ~1.8 hours max duration, decisecond updates +// NEO_SECONDS 1000 // ~18.2 hours max duration, second updates +// NEO_DECASECONDS 10000 // ~7.5 days, 10 second updates +// + +#ifdef ARDUINO_ARCH_AVR +// for AVR, you need to manage the state due to lack of STL/compiler support +// for Esp8266 you can define the function using a lambda and state is created for you +// see below for an example +struct MyAnimationState +{ + RgbColor StartingColor; // the color the animation starts at + RgbColor EndingColor; // the color the animation will end at + AnimEaseFunction Easeing; // the acceleration curve it will use +}; + +MyAnimationState animationState[PixelCount]; +// one entry per pixel to match the animation timing manager + +void AnimUpdate(const AnimationParam& param) +{ + // first apply an easing (curve) to the animation + // this simulates acceleration to the effect + float progress = animationState[param.index].Easeing(param.progress); + + // this gets called for each animation on every time step + // progress will start at 0.0 and end at 1.0 + // we use the blend function on the RgbColor to mix + // color based on the progress given to us in the animation + RgbColor updatedColor = RgbColor::LinearBlend( + animationState[param.index].StartingColor, + animationState[param.index].EndingColor, + progress); + // apply the color to the strip + strip.SetPixelColor(param.index, updatedColor); +} +#endif + +void SetRandomSeed() +{ + uint32_t seed; + + // random works best with a seed that can use 31 bits + // analogRead on a unconnected pin tends toward less than four bits + seed = analogRead(0); + delay(1); + + for (int shifts = 3; shifts < 31; shifts += 3) + { + seed ^= analogRead(0) << shifts; + delay(1); + } + + // Serial.println(seed); + randomSeed(seed); +} + +void setup() +{ + Serial.begin(115200); + while (!Serial); // wait for serial attach + + strip.Begin(); + strip.Show(); + + SetRandomSeed(); + + // just pick some colors + for (uint16_t pixel = 0; pixel < PixelCount; pixel++) + { + RgbColor color = RgbColor(random(255), random(255), random(255)); + strip.SetPixelColor(pixel, color); + } + + Serial.println(); + Serial.println("Running..."); +} + + +void SetupAnimationSet() +{ + // setup some animations + for (uint16_t pixel = 0; pixel < PixelCount; pixel++) + { + const uint8_t peak = 128; + + // pick a random duration of the animation for this pixel + // since values are centiseconds, the range is 1 - 4 seconds + uint16_t time = random(100, 400); + + // each animation starts with the color that was present + RgbColor originalColor = strip.GetPixelColor(pixel); + // and ends with a random color + RgbColor targetColor = RgbColor(random(peak), random(peak), random(peak)); + // with the random ease function + AnimEaseFunction easing; + + switch (random(3)) + { + case 0: + easing = NeoEase::CubicIn; + break; + case 1: + easing = NeoEase::CubicOut; + break; + case 2: + easing = NeoEase::QuadraticInOut; + break; + } + +#ifdef ARDUINO_ARCH_AVR + // each animation starts with the color that was present + animationState[pixel].StartingColor = originalColor; + // and ends with a random color + animationState[pixel].EndingColor = targetColor; + // using the specific curve + animationState[pixel].Easeing = easing; + + // now use the animation state we just calculated and start the animation + // which will continue to run and call the update function until it completes + animations.StartAnimation(pixel, time, AnimUpdate); +#else + // we must supply a function that will define the animation, in this example + // we are using "lambda expression" to define the function inline, which gives + // us an easy way to "capture" the originalColor and targetColor for the call back. + // + // this function will get called back when ever the animation needs to change + // the state of the pixel, it will provide a animation progress value + // from 0.0 (start of animation) to 1.0 (end of animation) + // + // we use this progress value to define how we want to animate in this case + // we call RgbColor::LinearBlend which will return a color blended between + // the values given, by the amount passed, hich is also a float value from 0.0-1.0. + // then we set the color. + // + // There is no need for the MyAnimationState struct as the compiler takes care + // of those details for us + AnimUpdateCallback animUpdate = [=](const AnimationParam& param) + { + // progress will start at 0.0 and end at 1.0 + // we convert to the curve we want + float progress = easing(param.progress); + + // use the curve value to apply to the animation + RgbColor updatedColor = RgbColor::LinearBlend(originalColor, targetColor, progress); + strip.SetPixelColor(pixel, updatedColor); + }; + + // now use the animation properties we just calculated and start the animation + // which will continue to run and call the update function until it completes + animations.StartAnimation(pixel, time, animUpdate); +#endif + } +} + +void loop() +{ + if (animations.IsAnimating()) + { + // the normal loop just needs these two to run the active animations + animations.UpdateAnimations(); + strip.Show(); + } + else + { + Serial.println(); + Serial.println("Setup Next Set..."); + // example function that sets up some animations + SetupAnimationSet(); + } +} + diff --git a/examples/NeoPixelFun/NeoPixelFun.pde b/examples/NeoPixelFun/NeoPixelFun.pde deleted file mode 100644 index b97ec402..00000000 --- a/examples/NeoPixelFun/NeoPixelFun.pde +++ /dev/null @@ -1,139 +0,0 @@ -#include - -#define pixelCount 4 - -NeoPixelBus strip = NeoPixelBus(pixelCount, 8); -uint16_t effectState = 0; - - -void setup() -{ - strip.Begin(); - strip.Show(); - SetRandomSeed(); -} - - -void loop() -{ - // There are three fun functions that implement different effects - // uncomment one at a time and upload to see the effect - - // LoopAround(192, 200); // very interesting on rings of NeoPixels - PickRandom(128); - // FadeInFadeOutRinseRepeat(192); - - // start animating - strip.StartAnimating(); - - // wait until no more animations are running - while (strip.IsAnimating()) - { - strip.UpdateAnimations(); - strip.Show(); - delay(31); // ~30hz change cycle - } - -} - -void FadeInFadeOutRinseRepeat(uint8_t peak) -{ - if (effectState == 0) - { - for (uint8_t pixel = 0; pixel < pixelCount; pixel++) - { - uint16_t time = random(800,1000); - strip.LinearFadePixelColor(time, pixel, RgbColor(random(peak), random(peak), random(peak))); - } - } - else if (effectState == 1) - { - for (uint8_t pixel = 0; pixel < pixelCount; pixel++) - { - uint16_t time = random(600,700); - strip.LinearFadePixelColor(time, pixel, RgbColor(0, 0, 0)); - } - } - effectState = (effectState + 1) % 2; // next effectState and keep within the number of effectStates - -} - -void PickRandom(uint8_t peak) -{ - - // pick random set of pixels to animate - uint8_t count = random(pixelCount); - while (count > 0) - { - uint8_t pixel = random(pixelCount); - - // configure the animations - RgbColor color; // = strip.getPixelColor(pixel); - - color = RgbColor(random(peak), random(peak), random(peak)); - - - uint16_t time = random(100,400); - strip.LinearFadePixelColor( time, pixel, color); - - count--; - } -} - -void LoopAround(uint8_t peak, uint16_t speed) -{ - // Looping around the ring sample - uint16_t prevPixel; - RgbColor prevColor; - - // fade previous one dark - prevPixel = (effectState + (pixelCount - 5)) % pixelCount; - strip.LinearFadePixelColor(speed, prevPixel, RgbColor(0, 0, 0)); - - // fade previous one dark - prevPixel = (effectState + (pixelCount - 4)) % pixelCount; - prevColor = strip.GetPixelColor( prevPixel ); - prevColor.Darken(prevColor.CalculateBrightness() / 2); - strip.LinearFadePixelColor(speed, prevPixel, prevColor); - - // fade previous one dark - prevPixel = (effectState + (pixelCount - 3)) % pixelCount; - prevColor = strip.GetPixelColor( prevPixel ); - prevColor.Darken(prevColor.CalculateBrightness() / 2); - strip.LinearFadePixelColor(speed, prevPixel, prevColor); - - // fade previous one dark - prevPixel = (effectState + (pixelCount - 2)) % pixelCount; - prevColor = strip.GetPixelColor( prevPixel ); - prevColor.Darken(prevColor.CalculateBrightness() / 2); - strip.LinearFadePixelColor(speed, prevPixel, prevColor); - - // fade previous one dark - prevPixel = (effectState + (pixelCount - 1)) % pixelCount; - prevColor = strip.GetPixelColor( prevPixel ); - prevColor.Darken(prevColor.CalculateBrightness() / 2); - strip.LinearFadePixelColor(speed, prevPixel, prevColor); - - // fade current one light - strip.LinearFadePixelColor(speed, effectState, RgbColor(random(peak), random(peak), random(peak))); - effectState = (effectState + 1) % pixelCount; -} - -void SetRandomSeed() -{ - uint32_t seed; - - // random works best with a seed that can use 31 bits - // analogRead on a unconnected pin tends toward less than four bits - seed = analogRead(0); - delay(1); - - for (int shifts = 3; shifts < 31; shifts += 3) - { - seed ^= analogRead(0) << shifts; - delay(1); - } - - // Serial.println(seed); - randomSeed(seed); -} diff --git a/examples/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino b/examples/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino new file mode 100644 index 00000000..97744697 --- /dev/null +++ b/examples/NeoPixelFunFadeInOut/NeoPixelFunFadeInOut.ino @@ -0,0 +1,134 @@ +// NeoPixelFunFadeInOut +// This example will randomly pick a color and fade all pixels to that color, then +// it will fade them to black and restart over +// +// This example demonstrates the use of a single animation channel to animate all +// the pixels at once. +// +#include +#include + +const uint16_t PixelCount = 16; // make sure to set this to the number of pixels in your strip +const uint8_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266 +const uint8_t AnimationChannels = 1; // we only need one as all the pixels are animated at once + +NeoPixelBus strip(PixelCount, PixelPin); +// For Esp8266, the Pin is ignored and it uses GPIO3. +// There are other Esp8266 alternative methods that provide more pin options, but also have +// other side effects. +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod also ignores the pin parameter and uses GPI02 +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod will work with all but pin 16, but is not stable with WiFi +// being active + +NeoPixelAnimator animations(AnimationChannels); // NeoPixel animation management object + +uint16_t effectState = 0; // general purpose variable used to store effect state + + +// what is stored for state is specific to the need, in this case, the colors. +// basically what ever you need inside the animation update function +struct MyAnimationState +{ + RgbColor StartingColor; + RgbColor EndingColor; +}; + +// one entry per pixel to match the animation timing manager +MyAnimationState animationState[AnimationChannels]; + +void SetRandomSeed() +{ + uint32_t seed; + + // random works best with a seed that can use 31 bits + // analogRead on a unconnected pin tends toward less than four bits + seed = analogRead(0); + delay(1); + + for (int shifts = 3; shifts < 31; shifts += 3) + { + seed ^= analogRead(0) << shifts; + delay(1); + } + + randomSeed(seed); +} + +// simple blend function +void BlendAnimUpdate(const AnimationParam& param) +{ + // this gets called for each animation on every time step + // progress will start at 0.0 and end at 1.0 + // we use the blend function on the RgbColor to mix + // color based on the progress given to us in the animation + RgbColor updatedColor = RgbColor::LinearBlend( + animationState[param.index].StartingColor, + animationState[param.index].EndingColor, + param.progress); + + // apply the color to the strip + for (uint16_t pixel = 0; pixel < PixelCount; pixel++) + { + strip.SetPixelColor(pixel, updatedColor); + } +} + +void FadeInFadeOutRinseRepeat(float luminance) +{ + if (effectState == 0) + { + // Fade upto a random color + // we use HslColor object as it allows us to easily pick a hue + // with the same saturation and luminance so the colors picked + // will have similiar overall brightness + RgbColor target = HslColor(random(360) / 360.0f, 1.0f, luminance); + uint16_t time = random(800, 2000); + + animationState[0].StartingColor = strip.GetPixelColor(0); + animationState[0].EndingColor = target; + + animations.StartAnimation(0, time, BlendAnimUpdate); + } + else if (effectState == 1) + { + // fade to black + uint16_t time = random(600, 700); + + animationState[0].StartingColor = strip.GetPixelColor(0); + animationState[0].EndingColor = RgbColor(0); + + animations.StartAnimation(0, time, BlendAnimUpdate); + } + + // toggle to the next effect state + effectState = (effectState + 1) % 2; +} + +void setup() +{ + strip.Begin(); + strip.Show(); + + SetRandomSeed(); +} + +void loop() +{ + if (animations.IsAnimating()) + { + // the normal loop just needs these two to run the active animations + animations.UpdateAnimations(); + strip.Show(); + } + else + { + // no animation runnning, start some + // + FadeInFadeOutRinseRepeat(0.2f); // 0.0 = black, 0.25 is normal, 0.5 is bright + } +} + + + diff --git a/examples/NeoPixelFunLoop/NeoPixelFunLoop.ino b/examples/NeoPixelFunLoop/NeoPixelFunLoop.ino new file mode 100644 index 00000000..31e3cc97 --- /dev/null +++ b/examples/NeoPixelFunLoop/NeoPixelFunLoop.ino @@ -0,0 +1,140 @@ +// NeoPixelFunLoop +// This example will move a trail of light around a series of pixels. +// A ring formation of pixels looks best. +// The trail will have a slowly fading tail. +// +// This will demonstrate the use of the NeoPixelAnimator. +// It shows the advanced use an animation to control the modification and +// starting of other animations. +// It also shows the normal use of animating colors. +// It also demonstrates the ability to share an animation channel rather than +// hard code them to pixels. +// + +#include +#include + + +const uint16_t PixelCount = 16; // make sure to set this to the number of pixels in your strip +const uint16_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266 +const uint16_t AnimCount = 8; // we only need about 8 animations, one to track movement, and the rest to actually animate + +const uint16_t PixelFadeDuration = 400; // half a second +// one second divide by the number of pixels = loop once a second +const uint16_t NextPixelMoveDuration = 1000 / PixelCount; // how fast we move through the pixels + +NeoPixelBus strip(PixelCount, PixelPin); +// For Esp8266, the Pin is ignored and it uses GPIO3. +// There are other Esp8266 alternative methods that provide more pin options, but also have +// other side effects. +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod also ignores the pin parameter and uses GPI02 +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod will work with all but pin 16, but is not stable with WiFi +// being active + +// what is stored for state is specific to the need, in this case, the colors and +// the pixel to animate; +// basically what ever you need inside the animation update function +struct MyAnimationState +{ + RgbColor StartingColor; + RgbColor EndingColor; + uint16_t IndexPixel; // which pixel this animation is effecting +}; + +NeoPixelAnimator animations(AnimCount); // NeoPixel animation management object +MyAnimationState animationState[AnimCount]; +uint16_t frontPixel = 0; // the front of the loop +RgbColor frontColor; // the color at the front of the loop + +void SetRandomSeed() +{ + uint32_t seed; + + // random works best with a seed that can use 31 bits + // analogRead on a unconnected pin tends toward less than four bits + seed = analogRead(0); + delay(1); + + for (int shifts = 3; shifts < 31; shifts += 3) + { + seed ^= analogRead(0) << shifts; + delay(1); + } + + // Serial.println(seed); + randomSeed(seed); +} + +void FadeOutAnimUpdate(const AnimationParam& param) +{ + // this gets called for each animation on every time step + // progress will start at 0.0 and end at 1.0 + // we use the blend function on the RgbColor to mix + // color based on the progress given to us in the animation + RgbColor updatedColor = RgbColor::LinearBlend( + animationState[param.index].StartingColor, + animationState[param.index].EndingColor, + param.progress); + // apply the color to the strip + strip.SetPixelColor(animationState[param.index].IndexPixel, updatedColor); +} + +void LoopAnimUpdate(const AnimationParam& param) +{ + // wait for this animation to complete, + // we are using it as a timer of sorts + if (param.state == AnimationState_Completed) + { + // done, time to restart this position tracking animation/timer + animations.RestartAnimation(param.index); + + // pick the next pixel inline to start animating + // + frontPixel = (frontPixel + 1) % PixelCount; // increment and wrap + if (frontPixel == 1) + { + // we looped, lets pick a new front color + frontColor = HslColor(random(360) / 360.0f, 1.0f, 0.25f); + } + + uint16_t indexAnim; + // do we have an animation available to use to animate the next front pixel? + // if you see skipping, then either you are going to fast or need to increase + // the number of animation channels + if (animations.NextAvailableAnimation(&indexAnim, 0)) + { + animationState[indexAnim].StartingColor = frontColor; + animationState[indexAnim].EndingColor = RgbColor(0, 0, 0); + animationState[indexAnim].IndexPixel = frontPixel; + + animations.StartAnimation(indexAnim, PixelFadeDuration, FadeOutAnimUpdate); + } + } +} + +void setup() +{ + strip.Begin(); + strip.Show(); + + SetRandomSeed(); + + // we use the index 0 animation to time how often we move to the next + // pixel in the strip + animations.StartAnimation(0, NextPixelMoveDuration, LoopAnimUpdate); +} + + +void loop() +{ + // this is all that is needed to keep it running + // and avoiding using delay() is always a good thing for + // any timing related routines + animations.UpdateAnimations(); + strip.Show(); +} + + + diff --git a/examples/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino b/examples/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino new file mode 100644 index 00000000..afc8c47b --- /dev/null +++ b/examples/NeoPixelFunRandomChange/NeoPixelFunRandomChange.ino @@ -0,0 +1,118 @@ +// NeoPixelFunRandomChange +// This example will randomly select a number pixels and then +// start an animation to blend them from their current color to +// randomly selected a color +// + +#include +#include + +const uint16_t PixelCount = 16; // make sure to set this to the number of pixels in your strip +const uint8_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266 + +NeoPixelBus strip(PixelCount, PixelPin); +// For Esp8266, the Pin is ignored and it uses GPIO3. +// There are other Esp8266 alternative methods that provide more pin options, but also have +// other side effects. +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod also ignores the pin parameter and uses GPI02 +//NeoPixelBus strip(PixelCount, PixelPin); +// NeoEsp8266Uart800KbpsMethod will work with all but pin 16, but is not stable with WiFi +// being active + +NeoPixelAnimator animations(PixelCount); // NeoPixel animation management object + +// what is stored for state is specific to the need, in this case, the colors. +// Basically what ever you need inside the animation update function +struct MyAnimationState +{ + RgbColor StartingColor; + RgbColor EndingColor; +}; + +// one entry per pixel to match the animation timing manager +MyAnimationState animationState[PixelCount]; + +void SetRandomSeed() +{ + uint32_t seed; + + // random works best with a seed that can use 31 bits + // analogRead on a unconnected pin tends toward less than four bits + seed = analogRead(0); + delay(1); + + for (int shifts = 3; shifts < 31; shifts += 3) + { + seed ^= analogRead(0) << shifts; + delay(1); + } + + // Serial.println(seed); + randomSeed(seed); +} + +// simple blend function +void BlendAnimUpdate(const AnimationParam& param) +{ + // this gets called for each animation on every time step + // progress will start at 0.0 and end at 1.0 + // we use the blend function on the RgbColor to mix + // color based on the progress given to us in the animation + RgbColor updatedColor = RgbColor::LinearBlend( + animationState[param.index].StartingColor, + animationState[param.index].EndingColor, + param.progress); + // apply the color to the strip + strip.SetPixelColor(param.index, updatedColor); +} + +void PickRandom(float luminance) +{ + // pick random count of pixels to animate + uint16_t count = random(PixelCount); + while (count > 0) + { + // pick a random pixel + uint16_t pixel = random(PixelCount); + + // pick random time and random color + // we use HslColor object as it allows us to easily pick a color + // with the same saturation and luminance + uint16_t time = random(100, 400); + animationState[pixel].StartingColor = strip.GetPixelColor(pixel); + animationState[pixel].EndingColor = HslColor(random(360) / 360.0f, 1.0f, luminance); + + animations.StartAnimation(pixel, time, BlendAnimUpdate); + + count--; + } +} + +void setup() +{ + strip.Begin(); + strip.Show(); + + SetRandomSeed(); +} + + +void loop() +{ + if (animations.IsAnimating()) + { + // the normal loop just needs these two to run the active animations + animations.UpdateAnimations(); + strip.Show(); + } + else + { + // no animations runnning, start some + // + PickRandom(0.2f); // 0.0 = black, 0.25 is normal, 0.5 is bright + } +} + + + diff --git a/examples/NeoPixelTest/NeoPixelTest.ino b/examples/NeoPixelTest/NeoPixelTest.ino new file mode 100644 index 00000000..a45a87be --- /dev/null +++ b/examples/NeoPixelTest/NeoPixelTest.ino @@ -0,0 +1,133 @@ +// NeoPixelTest +// This example will cycle between showing four pixels as Red, Green, Blue, White +// and then showing those pixels as Black. +// +// Included but commented out are examples of configuring a NeoPixelBus for +// different color order including an extra white channel, different data speeds, and +// for Esp8266 different methods to send the data. +// NOTE: You will need to make sure to pick the one for your platform +// +// +// There is serial output of the current state so you can confirm and follow along +// + +#include + +const uint16_t PixelCount = 4; // this example assumes 4 pixels, making it smaller will cause a failure +const uint8_t PixelPin = 2; // make sure to set this to the correct pin, ignored for Esp8266 + +#define colorSaturation 128 + +// three element pixels, in different order and speeds +NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); + +// You can also use one of these for Esp8266, +// each having their own restrictions +// +// These two are the same as above as the DMA method is the default +// NOTE: These will ignore the PIN and use GPI03 pin +//NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); + +// Uart method is good for the Esp-01 or other pin restricted modules +// NOTE: These will ignore the PIN and use GPI02 pin +//NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); + +// The bitbang method is really only good if you are not using WiFi features of the ESP +// It works with all but pin 16 +//NeoPixelBus strip(PixelCount, PixelPin); +//NeoPixelBus strip(PixelCount, PixelPin); + +// four element pixels, RGBW +//NeoPixelBus strip(PixelCount, PixelPin); + +RgbColor red(colorSaturation, 0, 0); +RgbColor green(0, colorSaturation, 0); +RgbColor blue(0, 0, colorSaturation); +RgbColor white(colorSaturation); +RgbColor black(0); + +HslColor hslRed(red); +HslColor hslGreen(green); +HslColor hslBlue(blue); +HslColor hslWhite(white); +HslColor hslBlack(black); + + +void setup() +{ + Serial.begin(115200); + while (!Serial); // wait for serial attach + + Serial.println(); + Serial.println("Initializing..."); + Serial.flush(); + + // this resets all the neopixels to an off state + strip.Begin(); + strip.Show(); + + + Serial.println(); + Serial.println("Running..."); +} + + +void loop() +{ + delay(5000); + + Serial.println("Colors R, G, B, W..."); + + // set the colors, + // if they don't match in order, you need to use NeoGrbFeature feature + strip.SetPixelColor(0, red); + strip.SetPixelColor(1, green); + strip.SetPixelColor(2, blue); + strip.SetPixelColor(3, white); + // the following line demonstrates rgbw color support + // if the NeoPixels are rgbw types the following line will compile + // if the NeoPixels are anything else, the following line will give an error + //strip.SetPixelColor(3, RgbwColor(colorSaturation)); + strip.Show(); + + + delay(5000); + + Serial.println("Off ..."); + + // turn off the pixels + strip.SetPixelColor(0, black); + strip.SetPixelColor(1, black); + strip.SetPixelColor(2, black); + strip.SetPixelColor(3, black); + strip.Show(); + + delay(5000); + + Serial.println("HSL Colors R, G, B, W..."); + + // set the colors, + // if they don't match in order, you may need to use NeoGrbFeature feature + strip.SetPixelColor(0, hslRed); + strip.SetPixelColor(1, hslGreen); + strip.SetPixelColor(2, hslBlue); + strip.SetPixelColor(3, hslWhite); + strip.Show(); + + + delay(5000); + + Serial.println("Off again..."); + + // turn off the pixels + strip.SetPixelColor(0, hslBlack); + strip.SetPixelColor(1, hslBlack); + strip.SetPixelColor(2, hslBlack); + strip.SetPixelColor(3, hslBlack); + strip.Show(); + +} + diff --git a/examples/NeoPixelTest/NeoPixelTest.pde b/examples/NeoPixelTest/NeoPixelTest.pde deleted file mode 100644 index 8ef226d0..00000000 --- a/examples/NeoPixelTest/NeoPixelTest.pde +++ /dev/null @@ -1,44 +0,0 @@ -#include - -#define pixelCount 4 -#define colorSaturation 128 - -NeoPixelBus strip = NeoPixelBus(pixelCount, 8); - -RgbColor red = RgbColor(colorSaturation, 0, 0); -RgbColor green = RgbColor(0, colorSaturation, 0); -RgbColor blue = RgbColor(0, 0, colorSaturation); -RgbColor white = RgbColor(colorSaturation); -RgbColor black = RgbColor(0); - -void setup() -{ - // this resets all the neopixels to an off state - strip.Begin(); - strip.Show(); -} - - -void loop() -{ - delay(1000); - - // set the colors, - // if they don't match in order, you may need to use NEO_GRB flag - strip.SetPixelColor(0, red); - strip.SetPixelColor(1, green); - strip.SetPixelColor(2, blue); - strip.SetPixelColor(3, white); - strip.Show(); - - delay(3000); - - // turn off the pixels - strip.SetPixelColor(0, black); - strip.SetPixelColor(1, black); - strip.SetPixelColor(2, black); - strip.SetPixelColor(3, black); - strip.Show(); -} - - diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 00000000..54a04df7 --- /dev/null +++ b/keywords.txt @@ -0,0 +1,99 @@ +####################################### +# Syntax Coloring Map NeoPixelBus +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +NeoPixelBus KEYWORD1 +RgbwColor KEYWORD1 +RgbColor KEYWORD1 +HslColor KEYWORD1 +HsbColor KEYWORD1 +NeoGrbFeature KEYWORD1 +NeoRgbwFeature KEYWORD1 +NeoRgbFeature KEYWORD1 +NeoBrgFeature KEYWORD1 +Neo800KbpsMethod KEYWORD1 +Neo400KbpsMethod KEYWORD1 +NeoAvr800KbpsMethod KEYWORD1 +NeoAvr400KbpsMethod KEYWORD1 +NeoEsp8266Dma800Method KEYWORD1 +NeoEsp8266Dma400Method KEYWORD1 +NeoEsp8266Uart800Method KEYWORD1 +NeoEsp8266Uart400Method KEYWORD1 +NeoEsp8266BitBang800Method KEYWORD1 +NeoEsp8266BitBang400Method KEYWORD1 +NeoPixelAnimator KEYWORD1 +AnimUpdateCallback KEYWORD1 +AnimationParam KEYWORD1 +NeoEase KEYWORD1 +AnimEaseFunction KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +Begin KEYWORD2 +Show KEYWORD2 +CanShow KEYWORD2 +ClearTo KEYWORD2 +IsDirty KEYWORD2 +Dirty KEYWORD2 +ResetDirty KEYWORD2 +Pixels KEYWORD2 +PixelsSize KEYWORD2 +PixelCount KEYWORD2 +SetPixelColor KEYWORD2 +GetPixelColor KEYWORD2 +CalculateBrightness KEYWORD2 +Darken KEYWORD2 +Lighten KEYWORD2 +LinearBlend KEYWORD2 +IsAnimating KEYWORD2 +NextAvailableAnimation KEYWORD2 +StartAnimation KEYWORD2 +StopAnimation KEYWORD2 +RestartAnimation KEYWORD2 +IsAnimationActive KEYWORD2 +AnimationDuration KEYWORD2 +UpdateAnimations KEYWORD2 +IsPaused KEYWORD2 +Pause KEYWORD2 +Resume KEYWORD2 +TimeScale KEYWORD2 +QuadraticIn KEYWORD2 +QuadraticOut KEYWORD2 +QuadraticInOut KEYWORD2 +CubicIn KEYWORD2 +CubicOut KEYWORD2 +CubicInOut KEYWORD2 +QuarticIn KEYWORD2 +QuarticOut KEYWORD2 +QuarticInOut KEYWORD2 +QuinticIn KEYWORD2 +QuinticOut KEYWORD2 +QuinticInOut KEYWORD2 +SinusoidalIn KEYWORD2 +SinusoidalOut KEYWORD2 +SinusoidalInOut KEYWORD2 +ExponentialIn KEYWORD2 +ExponentialOut KEYWORD2 +ExponentialInOut KEYWORD2 +CircularIn KEYWORD2 +CircularOut KEYWORD2 +CircularInOut KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +NEO_MILLISECONDS LITERAL1 +NEO_CENTISECONDS LITERAL1 +NEO_DECISECONDS LITERAL1 +NEO_SECONDS LITERAL1 +NEO_DECASECONDS LITERAL1 +AnimationState_Started LITERAL1 +AnimationState_Progress LITERAL1 +AnimationState_Completed LITERAL1 diff --git a/library.json b/library.json index 4e2d9572..d4059da5 100644 --- a/library.json +++ b/library.json @@ -1,11 +1,13 @@ { - "name": "NeoPixelBus", - "description": "Adafruit enhanced NeoPixel support library (WS2811, WS2812)", - "keywords": "led, strip", - "frameworks": "arduino", - "platforms": "*", - "repository": { + "name": "NeoPixelBus by Makuna", + "keywords": "NeoPixel, WS2811, WS2812, SK6812, RGB, RGBW", + "description": "A library that makes controlling NeoPixels (WS2811, WS2812 & SK6812) easy. Supports most Arduino platforms. Support for RGBW pixels. Includes seperate RgbColor, RgbwColor, HslColor, and HsbColor objects. Includes an animator class that helps create asyncronous animations. For Esp8266 it has three methods of sending data, DMA, UART, and Bit Bang.", + "repository": + { "type": "git", - "url": "https://github.com/Makuna/NeoPixelBus.git" - } + "url": "https://github.com/Makuna/NeoPixelBus" + }, + "frameworks": "arduino", + "platforms": "*" } + diff --git a/library.properties b/library.properties new file mode 100644 index 00000000..4985a4e5 --- /dev/null +++ b/library.properties @@ -0,0 +1,9 @@ +name=NeoPixelBus by Makuna +version=2.0.0 +author=Michael C. Miller (makuna@live.com) +maintainer=Michael C. Miller (makuna@live.com) +sentence=A library that makes controlling NeoPixels (WS2811, WS2812 & SK6812) easy. +paragraph=Supports most Arduino platforms, and especially Esp8266. Support for RGBW pixels. Includes seperate RgbColor, RgbwColor, HslColor, and HsbColor objects. Includes an animator class that helps create asyncronous animations. For Esp8266 it has three methods of sending data, DMA, UART, and Bit Bang. +category=Display +url=https://github.com/Makuna/NeoPixelBus +architectures=* \ No newline at end of file diff --git a/src/HsbColor.cpp b/src/HsbColor.cpp new file mode 100644 index 00000000..cc0b3b26 --- /dev/null +++ b/src/HsbColor.cpp @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- +HsbColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include "RgbColor.h" +#include "HsbColor.h" + + +HsbColor::HsbColor(RgbColor color) +{ + // convert colors to float between (0.0 - 1.0) + float r = color.R / 255.0f; + float g = color.G / 255.0f; + float b = color.B / 255.0f; + + float max = (r > g && r > b) ? r : (g > b) ? g : b; + float min = (r < g && r < b) ? r : (g < b) ? g : b; + + float d = max - min; + + float h = 0.0; + float v = max; + float s = (v == 0.0f) ? 0 : (d / v); + + if (d != 0.0f) + { + if (r == max) + { + h = (g - b) / d + (g < b ? 6.0f : 0.0f); + } + else if (g == max) + { + h = (b - r) / d + 2.0f; + } + else + { + h = (r - g) / d + 4.0f; + } + h /= 6.0f; + } + + + H = h; + S = s; + B = v; +} + +HsbColor HsbColor::LinearBlend(HsbColor left, HsbColor right, float progress) +{ + return HsbColor(left.H + ((right.H - left.H) * progress), + left.S + ((right.S - left.S) * progress), + left.B + ((right.B - left.B) * progress)); +} \ No newline at end of file diff --git a/src/HsbColor.h b/src/HsbColor.h new file mode 100644 index 00000000..bf20a4ea --- /dev/null +++ b/src/HsbColor.h @@ -0,0 +1,75 @@ +/*------------------------------------------------------------------------- +HsbColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +#include + +// ------------------------------------------------------------------------ +// HsbColor represents a color object that is represented by Hue, Saturation, Brightness +// component values. It contains helpful color routines to manipulate the +// color. +// ------------------------------------------------------------------------ +struct HsbColor +{ + // ------------------------------------------------------------------------ + // Construct a HsbColor using H, S, B values (0.0 - 1.0) + // ------------------------------------------------------------------------ + HsbColor(float h, float s, float b) : + H(h), S(s), B(b) + { + }; + + // ------------------------------------------------------------------------ + // Construct a HsbColor using RgbColor + // ------------------------------------------------------------------------ + HsbColor(RgbColor color); + + // ------------------------------------------------------------------------ + // Construct a HsbColor that will have its values set in latter operations + // CAUTION: The H,S,B members are not initialized and may not be consistent + // ------------------------------------------------------------------------ + HsbColor() + { + }; + + // ------------------------------------------------------------------------ + // LinearBlend between two colors by the amount defined by progress variable + // left - the color to start the blend at + // right - the color to end the blend at + // progress - (0.0 - 1.0) value where 0.0 will return left and 1.0 will return right + // and a value between will blend the color weighted linearly between them + // ------------------------------------------------------------------------ + static HsbColor LinearBlend(HsbColor left, HsbColor right, float progress); + + // ------------------------------------------------------------------------ + // Hue, Saturation, Brightness color members + // ------------------------------------------------------------------------ + + float H; + float S; + float B; +}; + diff --git a/src/HslColor.cpp b/src/HslColor.cpp new file mode 100644 index 00000000..3842ab49 --- /dev/null +++ b/src/HslColor.cpp @@ -0,0 +1,78 @@ +/*------------------------------------------------------------------------- +HslColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include "RgbColor.h" +#include "HslColor.h" + + +HslColor::HslColor(RgbColor color) +{ + // convert colors to float between (0.0 - 1.0) + float r = color.R / 255.0f; + float g = color.G / 255.0f; + float b = color.B / 255.0f; + + float max = (r > g && r > b) ? r : (g > b) ? g : b; + float min = (r < g && r < b) ? r : (g < b) ? g : b; + + float h, s, l; + l = (max + min) / 2.0f; + + if (max == min) + { + h = s = 0.0f; + } + else + { + float d = max - min; + s = (l > 0.5f) ? d / (2.0f - (max + min)) : d / (max + min); + + if (r > g && r > b) + { + h = (g - b) / d + (g < b ? 6.0f : 0.0f); + } + else if (g > b) + { + h = (b - r) / d + 2.0f; + } + else + { + h = (r - g) / d + 4.0f; + } + h /= 6.0f; + } + + H = h; + S = s; + L = l; +} + +HslColor HslColor::LinearBlend(HslColor left, HslColor right, float progress) +{ + return HslColor(left.H + ((right.H - left.H) * progress), + left.S + ((right.S - left.S) * progress), + left.L + ((right.L - left.L) * progress)); +} \ No newline at end of file diff --git a/src/HslColor.h b/src/HslColor.h new file mode 100644 index 00000000..54c620b5 --- /dev/null +++ b/src/HslColor.h @@ -0,0 +1,76 @@ +/*------------------------------------------------------------------------- +HslColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +#include + +// ------------------------------------------------------------------------ +// HslColor represents a color object that is represented by Hue, Saturation, Lightness +// component values. It contains helpful color routines to manipulate the +// color. +// ------------------------------------------------------------------------ +struct HslColor +{ + + // ------------------------------------------------------------------------ + // Construct a HslColor using H, S, L values (0.0 - 1.0) + // L should be limited to between (0.0 - 0.5) + // ------------------------------------------------------------------------ + HslColor(float h, float s, float l) : + H(h), S(s), L(l) + { + }; + + // ------------------------------------------------------------------------ + // Construct a HslColor using RgbColor + // ------------------------------------------------------------------------ + HslColor(RgbColor color); + + // ------------------------------------------------------------------------ + // Construct a HslColor that will have its values set in latter operations + // CAUTION: The H,S,L members are not initialized and may not be consistent + // ------------------------------------------------------------------------ + HslColor() + { + }; + + // ------------------------------------------------------------------------ + // LinearBlend between two colors by the amount defined by progress variable + // left - the color to start the blend at + // right - the color to end the blend at + // progress - (0.0 - 1.0) value where 0.0 will return left and 1.0 will return right + // and a value between will blend the color weighted linearly between them + // ------------------------------------------------------------------------ + static HslColor LinearBlend(HslColor left, HslColor right, float progress); + + // ------------------------------------------------------------------------ + // Hue, Saturation, Lightness color members + // ------------------------------------------------------------------------ + float H; + float S; + float L; +}; + diff --git a/src/NeoArmMethod.h b/src/NeoArmMethod.h new file mode 100644 index 00000000..08cb52e2 --- /dev/null +++ b/src/NeoArmMethod.h @@ -0,0 +1,624 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for ARM MCUs. +Teensy 3.0, 3.1, LC, Arduino Due + +Written by Michael C. Miller. +Some work taken from the Adafruit NeoPixel library. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. +The contents of this file were taken from the Adafruit NeoPixel library +and modified only to fit within individual calling functions. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#if defined(__arm__) + +template class NeoArmMethodBase +{ +public: + NeoArmMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) : + _pin(pin) + { + pinMode(pin, OUTPUT); + + _sizePixels = pixelCount * elementSize; + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0, _sizePixels); + } + + ~NeoArmMethodBase() + { + pinMode(_pin, INPUT); + + free(_pixels); + } + + bool IsReadyToUpdate() const + { + uint32_t delta = micros() - _endTime; + + return (delta >= 50L); + } + + void Initialize() + { + digitalWrite(_pin, LOW); + + _endTime = micros(); + } + + void Update() + { + // 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 (!IsReadyToUpdate()) + { + yield(); // allows for system yield if needed + } + + noInterrupts(); // Need 100% focus on instruction timing + + T_SPEED::send_pixels(_pixels, _sizePixels, _pin); + + interrupts(); + + // save EOD time for latch on next call + _endTime = micros(); + } + + uint8_t* getPixels() const + { + return _pixels; + }; + + size_t getPixelsSize() const + { + return _sizePixels; + }; + +private: + uint32_t _endTime; // Latch timing reference + size_t _sizePixels; // Size of '_pixels' buffer below + uint8_t* _pixels; // Holds LED color values + uint8_t _pin; // output pin number +}; + + +#if defined(__MK20DX128__) || defined(__MK20DX256__) // Teensy 3.0 & 3.1 + +class NeoArmMk20dxSpeedProps800Kbps +{ +public: + static const uint32_t CyclesT0h = (F_CPU / 4000000); + static const uint32_t CyclesT1h = (F_CPU / 1250000); + static const uint32_t Cycles = (F_CPU / 800000); +}; + +class NeoArmMk20dxSpeedProps400Kbps +{ +public: + static const uint32_t CyclesT0h = (F_CPU / 2000000); + static const uint32_t CyclesT1h = (F_CPU / 833333); + static const uint32_t Cycles = (F_CPU / 400000); +}; + +template class NeoArmMk20dxSpeedBase +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, uint8_t pin) + { + uint8_t* p = pixels; + uint8_t* end = p + sizePixels; + uint8_t pix; + uint8_t mask; + + volatile uint8_t* set = portSetRegister(pin); + volatile uint8_t* clr = portClearRegister(pin); + + uint32_t cyc; + + ARM_DEMCR |= ARM_DEMCR_TRCENA; + ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA; + + cyc = ARM_DWT_CYCCNT + T_SPEEDPROPS::Cycles; + while (p < end) + { + pix = *p++; + for (mask = 0x80; mask; mask >>= 1) + { + while (ARM_DWT_CYCCNT - cyc < T_SPEEDPROPS::Cycles); + + cyc = ARM_DWT_CYCCNT; + *set = 1; + if (pix & mask) + { + while (ARM_DWT_CYCCNT - cyc < T_SPEEDPROPS::CyclesT1h); + } + else + { + while (ARM_DWT_CYCCNT - cyc < T_SPEEDPROPS::CyclesT0h); + } + *clr = 1; + } + } + } +}; + +typedef NeoArmMethodBase> NeoArm800KbpsMethod; +typedef NeoArmMethodBase> NeoArm400KbpsMethod; + +#elif defined(__MKL26Z64__) // Teensy-LC + +#if F_CPU == 48000000 + + + +class NeoArmMk26z64Speed800Kbps +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, uint8_t pin) + { + uint8_t* p = pixels; + uint8_t pix; + uint8_t count; + uint8_t dly; + uint8_t bitmask = digitalPinToBitMask(pin); + volatile uint8_t* reg = portSetRegister(pin); + uint32_t num = sizePixels; + + asm volatile( + "L%=_begin:" "\n\t" + "ldrb %[pix], [%[p], #0]" "\n\t" + "lsl %[pix], #24" "\n\t" + "movs %[count], #7" "\n\t" + "L%=_loop:" "\n\t" + "lsl %[pix], #1" "\n\t" + "bcs L%=_loop_one" "\n\t" + "L%=_loop_zero:" + "strb %[bitmask], [%[reg], #0]" "\n\t" + "movs %[dly], #4" "\n\t" + "L%=_loop_delay_T0H:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_loop_delay_T0H" "\n\t" + "strb %[bitmask], [%[reg], #4]" "\n\t" + "movs %[dly], #13" "\n\t" + "L%=_loop_delay_T0L:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_loop_delay_T0L" "\n\t" + "b L%=_next" "\n\t" + "L%=_loop_one:" + "strb %[bitmask], [%[reg], #0]" "\n\t" + "movs %[dly], #13" "\n\t" + "L%=_loop_delay_T1H:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_loop_delay_T1H" "\n\t" + "strb %[bitmask], [%[reg], #4]" "\n\t" + "movs %[dly], #4" "\n\t" + "L%=_loop_delay_T1L:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_loop_delay_T1L" "\n\t" + "nop" "\n\t" + "L%=_next:" "\n\t" + "sub %[count], #1" "\n\t" + "bne L%=_loop" "\n\t" + "lsl %[pix], #1" "\n\t" + "bcs L%=_last_one" "\n\t" + "L%=_last_zero:" + "strb %[bitmask], [%[reg], #0]" "\n\t" + "movs %[dly], #4" "\n\t" + "L%=_last_delay_T0H:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_last_delay_T0H" "\n\t" + "strb %[bitmask], [%[reg], #4]" "\n\t" + "movs %[dly], #10" "\n\t" + "L%=_last_delay_T0L:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_last_delay_T0L" "\n\t" + "b L%=_repeat" "\n\t" + "L%=_last_one:" + "strb %[bitmask], [%[reg], #0]" "\n\t" + "movs %[dly], #13" "\n\t" + "L%=_last_delay_T1H:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_last_delay_T1H" "\n\t" + "strb %[bitmask], [%[reg], #4]" "\n\t" + "movs %[dly], #1" "\n\t" + "L%=_last_delay_T1L:" "\n\t" + "sub %[dly], #1" "\n\t" + "bne L%=_last_delay_T1L" "\n\t" + "nop" "\n\t" + "L%=_repeat:" "\n\t" + "add %[p], #1" "\n\t" + "sub %[num], #1" "\n\t" + "bne L%=_begin" "\n\t" + "L%=_done:" "\n\t" + : [p] "+r" (p), + [pix] "=&r" (pix), + [count] "=&r" (count), + [dly] "=&r" (dly), + [num] "+r" (num) + : [bitmask] "r" (bitmask), + [reg] "r" (reg) + ); + } +}; + +typedef NeoArmMethodBase NeoArm800KbpsMethod; + +#else +#error "Teensy-LC: Sorry, only 48 MHz is supported, please set Tools > CPU Speed to 48 MHz" +#endif // F_CPU == 48000000 + +#elif defined(__SAMD21G18A__) // Arduino Zero + + +class NeoArmSamd21g18aSpeedProps800Kbps +{ +public: + static void BitPreWait() + { + asm("nop; nop; nop; nop; nop; nop; nop; nop;"); + } + static void BitT1hWait() + { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + } + static void BitT0lWait() + { + asm("nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + } + static void BitPostWait() + { + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop;"); + } +}; + +class NeoArmSamd21g18aSpeedProps400Kbps +{ +public: + static void BitPreWait() + { + asm("nop; nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;"); + } + static void BitT1hWait() + { + 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;"); + } + static void BitT0lWait() + { + 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;"); + } + static void BitPostWait() + { + asm("nop; nop; nop; nop; nop; nop; nop;"); + } +}; + +template class NeoArmSamd21g18aSpeedBase +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, uint8_t pin) + { + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... + uint8_t* ptr = pixels; + uint8_t* end = ptr + sizePixels;; + uint8_t p = *ptr++; + uint8_t bitMask = 0x80; + uint8_t portNum = g_APinDescription[pin].ulPort; + uint32_t pinMask = 1ul << g_APinDescription[pin].ulPin; + + volatile uint32_t* set = &(PORT->Group[portNum].OUTSET.reg); + volatile uint32_t* clr = &(PORT->Group[portNum].OUTCLR.reg); + + for (;;) + { + *set = pinMask; + T_SPEEDPROPS::BitPreWait(); + + if (p & bitMask) + { + T_SPEEDPROPS::BitT1hWait(); + *clr = pinMask; + } + else + { + *clr = pinMask; + T_SPEEDPROPS::BitT0lWait(); + } + if (bitMask >>= 1) + { + T_SPEEDPROPS::BitPostWait(); + } + else + { + if (ptr >= end) + { + break; + } + p = *ptr++; + bitMask = 0x80; + } + } + } +}; + +typedef NeoArmMethodBase> NeoArm800KbpsMethod; +typedef NeoArmMethodBase> NeoArm400KbpsMethod; + +#elif defined (ARDUINO_STM32_FEATHER) // FEATHER WICED (120MHz) + + + +class NeoArmStm32SpeedProps800Kbps +{ + static void BitT1hWait() + { + 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;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop; nop; nop;"); + } + static void BitT1lWait() + { + 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;"); + } + static void BitT0hWait() + { + 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;"); + } + static void BitT0lWait() + { + 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;" + "nop; nop; nop; nop; nop; nop; nop; nop;" + "nop; nop; nop; nop;"); + } + + +}; +/* TODO - not found in Adafruit library +class NeoArmStm32SpeedProps400Kbps +{ +static void BitT1hWait() +{ +} +static void BitT1lWait() +{ +} +static void BitT0hWait() +{ +} +static void BitT0lWait() +{ +} +}; +*/ + +template class NeoArmStm32SpeedBase +{ + static void send_pixels(uint8_t* pixels, size_t sizePixels, uint8_t pin) + { + // Tried this with a timer/counter, couldn't quite get adequate + // resolution. So yay, you get a load of goofball NOPs... + + uint8_t* ptr = pixels; + uint8_t* end = ptr + sizePixels; + uint8_t p = *ptr++; + uint8_t bitMask = 0x80; + uint32_t pinMask = BIT(PIN_MAP[pin].gpio_bit); + + volatile uint16_t* set = &(PIN_MAP[pin].gpio_device->regs->BSRRL); + volatile uint16_t* clr = &(PIN_MAP[pin].gpio_device->regs->BSRRH); + + for (;;) + { + if (p & bitMask) + { + // ONE + // High 800ns + *set = pinMask; + T_SPEEDPROPS::BitT1hWait(); + // Low 450ns + *clr = pinMask; + T_SPEEDPROPS::BitT1lWait(); + } + else + { + // ZERO + // High 400ns + *set = pinMask; + T_SPEEDPROPS::BitT0hWait(); + // Low 850ns + *clr = pinMask; + T_SPEEDPROPS::BitT0lWait(); + } + if (bitMask >>= 1) + { + // Move on to the next pixel + asm("nop;"); + } + else + { + if (ptr >= end) + { + break; + } + + p = *ptr++; + bitMask = 0x80; + } + } + } +}; + +typedef NeoArmMethodBase> NeoArm800KbpsMethod; + +#else // Other ARM architecture -- Presumed Arduino Due + + +#define ARM_OTHER_SCALE VARIANT_MCK / 2UL / 1000000UL +#define ARM_OTHER_INST (2UL * F_CPU / VARIANT_MCK) + +class NeoArmOtherSpeedProps800Kbps +{ +public: + static const uint32_t CyclesT0h = ((uint32_t)(0.40 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); + static const uint32_t CyclesT1h = ((uint32_t)(0.80 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); + static const uint32_t Cycles = ((uint32_t)(1.25 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); +}; + +class NeoArmOtherSpeedProps400Kbps +{ +public: + static const uint32_t CyclesT0h = ((uint32_t)(0.50 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); + static const uint32_t CyclesT1h = ((uint32_t)(1.20 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); + static const uint32_t Cycles = ((uint32_t)(2.50 * ARM_OTHER_SCALE + 0.5) - (5 * ARM_OTHER_INST)); +}; + +template class NeoArmOtherSpeedBase +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, uint8_t pin) + { + uint32_t pinMask; + uint32_t t; + Pio* port; + volatile WoReg* portSet; + volatile WoReg* portClear; + volatile WoReg* timeValue; + volatile WoReg* timeReset; + uint8_t* p; + uint8_t* end; + uint8_t pix; + uint8_t mask; + + pmc_set_writeprotect(false); + pmc_enable_periph_clk((uint32_t)TC3_IRQn); + + TC_Configure(TC1, 0, + TC_CMR_WAVE | TC_CMR_WAVSEL_UP | TC_CMR_TCCLKS_TIMER_CLOCK1); + TC_Start(TC1, 0); + + pinMask = g_APinDescription[pin].ulPin; // Don't 'optimize' these into + port = g_APinDescription[pin].pPort; // declarations above. Want to + portSet = &(port->PIO_SODR); // burn a few cycles after + portClear = &(port->PIO_CODR); // starting timer to minimize + timeValue = &(TC1->TC_CHANNEL[0].TC_CV); // the initial 'while'. + timeReset = &(TC1->TC_CHANNEL[0].TC_CCR); + p = pixels; + end = p + sizePixels; + pix = *p++; + mask = 0x80; + + for (;;) + { + if (pix & mask) + { + t = T_SPEEDPROPS::CyclesT1h; + } + else + { + t = T_SPEEDPROPS::CyclesT0h; + } + + // wait for the end of the previous cycle + while (*timeValue < T_SPEEDPROPS::Cycles); + + *portSet = pinMask; + *timeReset = TC_CCR_CLKEN | TC_CCR_SWTRG; + + while (*timeValue < t); + + *portClear = pinMask; + if (!(mask >>= 1)) + { + // This 'inside-out' loop logic utilizes + if (p >= end) + { + break; // idle time to minimize inter-byte delays. + } + pix = *p++; + mask = 0x80; + } + } + + // not really needed as the wait for latch does this and + // while (*timeValue < T_SPEEDPROPS::Cycles); // Wait for last bit + + TC_Stop(TC1, 0); + } +}; + +typedef NeoArmMethodBase> NeoArm800KbpsMethod; +typedef NeoArmMethodBase> NeoArm400KbpsMethod; + +#endif + + +// Arm doesn't have alternatives methods yet, so only one to make the default +typedef NeoArm800KbpsMethod Neo800KbpsMethod; +#ifdef NeoArm400KbpsMethod // this is needed due to missing 400Kbps for some platforms +typedef NeoArm400KbpsMethod Neo400KbpsMethod; +#endif + +#endif // defined(__arm__) + diff --git a/src/NeoAvrMethod.h b/src/NeoAvrMethod.h new file mode 100644 index 00000000..408e8814 --- /dev/null +++ b/src/NeoAvrMethod.h @@ -0,0 +1,184 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Atmel AVR. + +Written by Michael C. Miller. +Some work taken from the Adafruit NeoPixel library. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_AVR + +extern "C" +{ + void send_pixels_8mhz_800_PortD(uint8_t* pixels, size_t sizePixels, uint8_t pinMask); + void send_pixels_8mhz_800_PortB(uint8_t* pixels, size_t sizePixels, uint8_t pinMask); + void send_pixels_8mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask); + void send_pixels_12mhz_800_PortD(uint8_t* pixels, size_t sizePixels, uint8_t pinMask); + void send_pixels_12mhz_800_PortB(uint8_t* pixels, size_t sizePixels, uint8_t pinMask); + void send_pixels_12mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask); + void send_pixels_16mhz_800(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask); + void send_pixels_16mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask); +} + +class NeoAvrSpeed800Kbps +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, 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_pixels_8mhz_800_PortD(pixels, sizePixels, pinMask); + else if (port == &PORTB) +#endif // PORTD + send_pixels_8mhz_800_PortB(pixels, sizePixels, pinMask); + +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) // 12Mhz CPU +#ifdef PORTD // PORTD + if (port == &PORTD) + send_pixels_12mhz_800_PortD(pixels, sizePixels, pinMask); + else if (port == &PORTB) +#endif // PORTD + send_pixels_12mhz_800_PortB(pixels, sizePixels, pinMask); + +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) // 16Mhz CPU + send_pixels_16mhz_800(pixels, sizePixels, port, pinMask); +#else +#error "CPU SPEED NOT SUPPORTED" +#endif + } +}; + +class NeoAvrSpeed400Kbps +{ +public: + static void send_pixels(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask) + { +#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) // 8Mhz CPU + send_pixels_8mhz_400(pixels, sizePixels, port, pinMask); + +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) // 12Mhz CPU + send_pixels_12mhz_400(pixels, sizePixels, port, pinMask); + +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) // 16Mhz CPU + send_pixels_16mhz_400(pixels, sizePixels, port, pinMask); +#else +#error "CPU SPEED NOT SUPPORTED" +#endif + } +}; + +template class NeoAvrMethodBase +{ +public: + NeoAvrMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) : + _pin(pin), + _port(NULL), + _pinMask(0) + { + pinMode(pin, OUTPUT); + + _sizePixels = pixelCount * elementSize; + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0, _sizePixels); + + _port = portOutputRegister(digitalPinToPort(pin)); + _pinMask = digitalPinToBitMask(pin); + } + + ~NeoAvrMethodBase() + { + pinMode(_pin, INPUT); + + free(_pixels); + } + + bool IsReadyToUpdate() const + { + uint32_t delta = micros() - _endTime; + + return (delta >= 50L); + } + + void Initialize() + { + digitalWrite(_pin, LOW); + + _endTime = micros(); + } + + void Update() + { + // 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 (!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 + + T_SPEED::send_pixels(_pixels, _sizePixels, _port, _pinMask); + + interrupts(); + + // save EOD time for latch on next call + _endTime = micros(); + } + + uint8_t* getPixels() const + { + return _pixels; + }; + + size_t getPixelsSize() const + { + return _sizePixels; + }; + +private: + uint32_t _endTime; // Latch timing reference + size_t _sizePixels; // Size of '_pixels' buffer below + uint8_t* _pixels; // Holds LED color values + uint8_t _pin; // output pin number + + volatile uint8_t* _port; // Output PORT register + uint8_t _pinMask; // Output PORT bitmask +}; + +typedef NeoAvrMethodBase NeoAvr800KbpsMethod; +typedef NeoAvrMethodBase NeoAvr400KbpsMethod; + +// AVR doesn't have alternatives yet, so there is just the default +typedef NeoAvr800KbpsMethod Neo800KbpsMethod; +typedef NeoAvr400KbpsMethod Neo400KbpsMethod; + +#endif + diff --git a/src/NeoColorFeatures.h b/src/NeoColorFeatures.h new file mode 100644 index 00000000..f5e637fe --- /dev/null +++ b/src/NeoColorFeatures.h @@ -0,0 +1,158 @@ +/*------------------------------------------------------------------------- +NeoPixelFeatures provides feature classes to describe color order and +color depth for NeoPixelBus template class + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +class Neo3Elements +{ +public: + static const size_t PixelSize = 3; + static const RgbColor Empty(); + + static uint8_t* getPixelAddress(uint8_t* pPixels, uint16_t indexPixel) + { + return &pPixels[indexPixel * PixelSize]; + } + + typedef RgbColor ColorObject; + +}; + +class Neo4Elements +{ +public: + static const size_t PixelSize = 4; + static const RgbColor Empty(); + + static uint8_t* getPixelAddress(uint8_t* pPixels, uint16_t indexPixel) + { + return &pPixels[indexPixel * PixelSize]; + } + + typedef RgbwColor ColorObject; +}; + +class NeoGrbFeature : public Neo3Elements +{ +public: + static void applyPixelColor(uint8_t* pPixels, uint16_t indexPixel, Neo3Elements::ColorObject color) + { + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + *p++ = color.G; + *p++ = color.R; + *p = color.B; + } + + static Neo3Elements::ColorObject retrievePixelColor(uint8_t* pPixels, uint16_t indexPixel) + { + Neo3Elements::ColorObject color; + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + color.G = *p++; + color.R = *p++; + color.B = *p; + + return color; + } +}; + +class NeoRgbwFeature : public Neo4Elements +{ +public: + static void applyPixelColor(uint8_t* pPixels, uint16_t indexPixel, Neo4Elements::ColorObject color) + { + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + *p++ = color.R; + *p++ = color.G; + *p++ = color.B; + *p = color.W; + } + + static Neo4Elements::ColorObject retrievePixelColor(uint8_t* pPixels, uint16_t indexPixel) + { + Neo4Elements::ColorObject color; + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + color.R = *p++; + color.G = *p++; + color.B = *p++; + color.W = *p; + + return color; + } +}; + +class NeoRgbFeature : public Neo3Elements +{ +public: + static void applyPixelColor(uint8_t* pPixels, uint16_t indexPixel, Neo3Elements::ColorObject color) + { + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + *p++ = color.R; + *p++ = color.G; + *p = color.B; + } + + static Neo3Elements::ColorObject retrievePixelColor(uint8_t* pPixels, uint16_t indexPixel) + { + Neo3Elements::ColorObject color; + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + color.R = *p++; + color.G = *p++; + color.B = *p; + + return color; + } +}; + +class NeoBrgFeature : public Neo3Elements +{ +public: + static void applyPixelColor(uint8_t* pPixels, uint16_t indexPixel, Neo3Elements::ColorObject color) + { + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + *p++ = color.B; + *p++ = color.R; + *p = color.G; + } + + static Neo3Elements::ColorObject retrievePixelColor(uint8_t* pPixels, uint16_t indexPixel) + { + Neo3Elements::ColorObject color; + uint8_t* p = getPixelAddress(pPixels, indexPixel); + + color.B = *p++; + color.R = *p++; + color.G = *p; + + return color; + } +}; diff --git a/src/NeoEase.h b/src/NeoEase.h new file mode 100644 index 00000000..e52869cf --- /dev/null +++ b/src/NeoEase.h @@ -0,0 +1,214 @@ +/*------------------------------------------------------------------------- +NeoEase provides animation curve equations for animation support. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_AVR + +typedef float(*AnimEaseFunction)(float linear); + +#else + +#undef max +#undef min +#include +typedef std::function AnimEaseFunction; + +#endif + +class NeoEase +{ +public: + static float QuadraticIn(float linear) + { + return linear * linear; + } + + static float QuadraticOut(float linear) + { + return (-linear * (linear - 2.0f)); + } + + static float QuadraticInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (0.5f * linear * linear); + } + else + { + linear -= 1.0f; + return (-0.5f * (linear * (linear - 2.0f) - 1.0f)); + } + } + + static float CubicIn(float linear) + { + return (linear * linear * linear); + } + + static float CubicOut(float linear) + { + linear -= 1.0f; + return (linear * linear * linear + 1); + } + + static float CubicInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (0.5f * linear * linear * linear); + } + else + { + linear -= 2.0f; + return (0.5f * (linear * linear * linear + 2.0f)); + } + } + + static float QuarticIn(float linear) + { + return (linear * linear * linear * linear); + } + + static float QuarticOut(float linear) + { + linear -= 1.0f; + return -(linear * linear * linear * linear - 1); + } + + static float QuarticInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (0.5f * linear * linear * linear * linear); + } + else + { + linear -= 2.0f; + return (-0.5f * (linear * linear * linear * linear - 2.0f)); + } + } + + static float QuinticIn(float linear) + { + return (linear * linear * linear * linear * linear); + } + + static float QuinticOut(float linear) + { + linear -= 1.0f; + return (linear * linear * linear * linear * linear + 1.0f); + } + + static float QuinticInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (0.5f * linear * linear * linear * linear * linear); + } + else + { + linear -= 2.0f; + return (0.5f * (linear * linear * linear * linear * linear + 2.0f)); + } + } + + static float SinusoidalIn(float linear) + { + return (-cos(linear * HALF_PI) + 1.0f); + } + + static float SinusoidalOut(float linear) + { + return (sin(linear * HALF_PI)); + } + + static float SinusoidalInOut(float linear) + { + return -0.5 * (cos(PI * linear) - 1.0f); + } + + static float ExponentialIn(float linear) + { + return (pow(2, 10.0f * (linear - 1.0f))); + } + + static float ExponentialOut(float linear) + { + return (-pow(2, -10.0f * linear) + 1.0f); + } + + static float ExponentialInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (0.5f * pow(2, 10.0f * (linear - 1.0f))); + } + else + { + linear -= 1.0f; + return (0.5f * (-pow(2, -10.0f * linear) + 2.0f)); + } + } + + static float CircularIn(float linear) + { + if (linear == 1.0f) + { + return 1.0f; + } + else + { + return (-(sqrt(1.0f - linear * linear) - 1.0f)); + } + } + + static float CircularOut(float linear) + { + linear -= 1.0f; + return (sqrt(1.0f - linear * linear)); + } + + static float CircularInOut(float linear) + { + linear *= 2.0f; + if (linear < 1.0f) + { + return (-0.5f * (sqrt(1.0f - linear * linear) - 1)); + } + else + { + linear -= 2.0f; + return (0.5f * (sqrt(1.0f - linear * linear) + 1.0f)); + } + } +}; \ No newline at end of file diff --git a/src/NeoEsp8266BitBangMethod.h b/src/NeoEsp8266BitBangMethod.h new file mode 100644 index 00000000..cefc4ee1 --- /dev/null +++ b/src/NeoEsp8266BitBangMethod.h @@ -0,0 +1,131 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_ESP8266 + +// due to linker overriding the ICACHE_RAM_ATTR for cpp files, these methods are +// moved into a C file so the attribute will be applied correctly +extern "C" void ICACHE_RAM_ATTR bitbang_send_pixels_800(uint8_t* pixels, uint8_t* end, uint8_t pin); +extern "C" void ICACHE_RAM_ATTR bitbang_send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin); + +class NeoEsp8266BitBangSpeed800Kbps +{ +public: + static void send_pixels(uint8_t* pixels, uint8_t* end, uint8_t pin) + { + bitbang_send_pixels_800(pixels, end, pin); + } +}; + +class NeoEsp8266BitBangSpeed400Kbps +{ +public: + static void send_pixels(uint8_t* pixels, uint8_t* end, uint8_t pin) + { + bitbang_send_pixels_400(pixels, end, pin); + } +}; + +template class NeoEsp8266BitBangMethodBase +{ +public: + NeoEsp8266BitBangMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) : + _pin(pin) + { + pinMode(pin, OUTPUT); + + _sizePixels = pixelCount * elementSize; + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0, _sizePixels); + } + + ~NeoEsp8266BitBangMethodBase() + { + pinMode(_pin, INPUT); + + free(_pixels); + } + + bool IsReadyToUpdate() const + { + uint32_t delta = micros() - _endTime; + + return (delta >= 50L); + } + + void Initialize() + { + digitalWrite(_pin, LOW); + + _endTime = micros(); + } + + void Update() + { + // 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 (!IsReadyToUpdate()) + { + yield(); // allows for system yield if needed + } + + noInterrupts(); // Need 100% focus on instruction timing + + T_SPEED::send_pixels(_pixels, _pixels + _sizePixels, _pin); + + interrupts(); + + // save EOD time for latch on next call + _endTime = micros(); + } + + uint8_t* getPixels() const + { + return _pixels; + }; + + size_t getPixelsSize() const + { + return _sizePixels; + }; + +private: + uint32_t _endTime; // Latch timing reference + size_t _sizePixels; // Size of '_pixels' buffer below + uint8_t* _pixels; // Holds LED color values + uint8_t _pin; // output pin number +}; + +typedef NeoEsp8266BitBangMethodBase NeoEsp8266BitBang800KbpsMethod; +typedef NeoEsp8266BitBangMethodBase NeoEsp8266BitBang400KbpsMethod; + +#endif \ No newline at end of file diff --git a/src/NeoEsp8266DmaMethod.h b/src/NeoEsp8266DmaMethod.h new file mode 100644 index 00000000..e83d688e --- /dev/null +++ b/src/NeoEsp8266DmaMethod.h @@ -0,0 +1,337 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. + + +Written by Michael C. Miller. +Thanks to g3gg0.de for porting the initial DMA support. +Thanks to github/cnlohr for the original work on DMA support, which is +located at https://github.com/cnlohr/esp8266ws2812i2s. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_ESP8266 + +extern "C" +{ +#include "Arduino.h" +#include "osapi.h" +#include "ets_sys.h" + +#include "i2s_reg.h" +#include "i2s.h" +#include "eagle_soc.h" +#include "esp8266_peri.h" +#include "slc_register.h" + +#include "osapi.h" +#include "ets_sys.h" +#include "user_interface.h" + + void rom_i2c_writeReg_Mask(uint32_t block, uint32_t host_id, uint32_t reg_add, uint32_t Msb, uint32_t Lsb, uint32_t indata); +} + +struct slc_queue_item +{ + uint32 blocksize : 12; + uint32 datalen : 12; + uint32 unused : 5; + uint32 sub_sof : 1; + uint32 eof : 1; + uint32 owner : 1; + uint32 buf_ptr; + uint32 next_link_ptr; +}; + +class NeoEsp8266DmaSpeed800Kbps +{ +public: + const static uint32_t I2sClockDivisor = 3; + const static uint32_t I2sBaseClockDivisor = 16; +}; + +class NeoEsp8266DmaSpeed400Kbps +{ +public: + const static uint32_t I2sClockDivisor = 6; + const static uint32_t I2sBaseClockDivisor = 16; +}; + +template class NeoEsp8266DmaMethodBase +{ +public: + NeoEsp8266DmaMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) + { + _sizePixels = pixelCount * elementSize; + _bitBufferSize = CalculateI2sBufferSize(pixelCount, elementSize); + + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0x00, _sizePixels); + + _i2sBlock = (uint8_t*)malloc(_bitBufferSize); + memset(_i2sBlock, 0x00, _bitBufferSize); + + memset(_i2sZeroes, 0x00, sizeof(_i2sZeroes)); + } + + ~NeoEsp8266DmaMethodBase() + { + free(_pixels); + free(_i2sBlock); + } + + bool IsReadyToUpdate() const + { + return IsStoppedDmaState(); + } + + void Initialize() + { + InitializeDma(); + } + + void InitializeDma() + { + // reset DMA registers + SET_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST); + CLEAR_PERI_REG_MASK(SLC_CONF0, SLC_RXLINK_RST); + + // clear all interrupt flags + SET_PERI_REG_MASK(SLC_INT_CLR, 0xffffffff); + + // set up DMA + CLEAR_PERI_REG_MASK(SLC_CONF0, (SLC_MODE << SLC_MODE_S)); + SET_PERI_REG_MASK(SLC_CONF0, (1 << SLC_MODE_S)); + SET_PERI_REG_MASK(SLC_RX_DSCR_CONF, SLC_INFOR_NO_REPLACE | SLC_TOKEN_NO_REPLACE); + CLEAR_PERI_REG_MASK(SLC_RX_DSCR_CONF, SLC_RX_FILL_EN | SLC_RX_EOF_MODE | SLC_RX_FILL_MODE); + + // prepare linked DMA descriptors, having EOF set for all + _i2sBufDescOut.owner = 1; + _i2sBufDescOut.eof = 1; + _i2sBufDescOut.sub_sof = 0; + _i2sBufDescOut.datalen = _bitBufferSize; + _i2sBufDescOut.blocksize = _bitBufferSize; + _i2sBufDescOut.buf_ptr = (uint32_t)_i2sBlock; + _i2sBufDescOut.unused = 0; + _i2sBufDescOut.next_link_ptr = (uint32_t)&_i2sBufDescLatch; + + // this zero-buffer block implements the latch/reset signal + _i2sBufDescLatch.owner = 1; + _i2sBufDescLatch.eof = 1; + _i2sBufDescLatch.sub_sof = 0; + _i2sBufDescLatch.datalen = sizeof(_i2sZeroes); + _i2sBufDescLatch.blocksize = sizeof(_i2sZeroes); + _i2sBufDescLatch.buf_ptr = (uint32_t)_i2sZeroes; + _i2sBufDescLatch.unused = 0; + _i2sBufDescLatch.next_link_ptr = (uint32_t)&_i2sBufDescStopped; + + // this empty block will stop the output and provide a flag that latch/reset has been sent + // it basically loops + _i2sBufDescStopped.owner = 1; + _i2sBufDescStopped.eof = 1; + _i2sBufDescStopped.sub_sof = 0; + _i2sBufDescStopped.datalen = sizeof(_i2sZeroes); + _i2sBufDescStopped.blocksize = sizeof(_i2sZeroes); + _i2sBufDescStopped.buf_ptr = (uint32_t)_i2sZeroes;; + _i2sBufDescStopped.unused = 0; + _i2sBufDescStopped.next_link_ptr = (uint32_t)&_i2sBufDescStopped; + + // configure the first descriptor + // TX_LINK isnt used, but has to be configured + CLEAR_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_DESCADDR_MASK); + CLEAR_PERI_REG_MASK(SLC_TX_LINK, SLC_TXLINK_DESCADDR_MASK); + SET_PERI_REG_MASK(SLC_RX_LINK, ((uint32)&_i2sBufDescOut) & SLC_RXLINK_DESCADDR_MASK); + SET_PERI_REG_MASK(SLC_TX_LINK, ((uint32)&_i2sBufDescOut) & SLC_TXLINK_DESCADDR_MASK); + + // we dont need interrupts + ETS_SLC_INTR_DISABLE(); + WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff); + + // configure RDX0/GPIO3 for output. it is the only supported pin unfortunately. + PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_I2SO_DATA); + + // configure I2S subsystem + I2S_CLK_ENABLE(); + CLEAR_PERI_REG_MASK(I2SCONF, I2S_I2S_RESET_MASK); + SET_PERI_REG_MASK(I2SCONF, I2S_I2S_RESET_MASK); + CLEAR_PERI_REG_MASK(I2SCONF, I2S_I2S_RESET_MASK); + + // Select 16bits per channel (FIFO_MOD=0), no DMA access (FIFO only) + CLEAR_PERI_REG_MASK(I2S_FIFO_CONF, I2S_I2S_DSCR_EN | + (I2S_I2S_RX_FIFO_MOD << I2S_I2S_RX_FIFO_MOD_S) | + (I2S_I2S_TX_FIFO_MOD << I2S_I2S_TX_FIFO_MOD_S)); + // Enable DMA in i2s subsystem + SET_PERI_REG_MASK(I2S_FIFO_CONF, I2S_I2S_DSCR_EN); + + // configure the rates + CLEAR_PERI_REG_MASK(I2SCONF, I2S_TRANS_SLAVE_MOD | + (I2S_BITS_MOD << I2S_BITS_MOD_S) | + (I2S_BCK_DIV_NUM << I2S_BCK_DIV_NUM_S) | + (I2S_CLKM_DIV_NUM << I2S_CLKM_DIV_NUM_S)); + SET_PERI_REG_MASK(I2SCONF, I2S_RIGHT_FIRST | I2S_MSB_RIGHT | I2S_RECE_SLAVE_MOD | + I2S_RECE_MSB_SHIFT | I2S_TRANS_MSB_SHIFT | + ((T_SPEED::I2sBaseClockDivisor & I2S_BCK_DIV_NUM) << I2S_BCK_DIV_NUM_S) | + ((T_SPEED::I2sClockDivisor & I2S_CLKM_DIV_NUM) << I2S_CLKM_DIV_NUM_S)); + + // disable all interrupts + SET_PERI_REG_MASK(I2SINT_CLR, I2S_I2S_RX_WFULL_INT_CLR | + I2S_I2S_PUT_DATA_INT_CLR | + I2S_I2S_TAKE_DATA_INT_CLR); + + // dma is now ready to start + } + + void Update() + { + FillBuffers(); + + // wait for latch to complete if it hasn't already + NullWait(); + + StopDma(); + StartDma(); + } + + uint8_t* getPixels() const + { + return _pixels; + }; + + size_t getPixelsSize() const + { + return _sizePixels; + } + +private: + + static uint32_t CalculateI2sBufferSize(uint16_t pixelCount, size_t elementSize) + { + // 4 I2S bytes per pixels byte + return ((uint32_t)pixelCount * elementSize * 4); + } + + void FillBuffers() + { + const uint16_t bitpatterns[16] = + { + 0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110, + 0b1000111010001000, 0b1000111010001110, 0b1000111011101000, 0b1000111011101110, + 0b1110100010001000, 0b1110100010001110, 0b1110100011101000, 0b1110100011101110, + 0b1110111010001000, 0b1110111010001110, 0b1110111011101000, 0b1110111011101110, + }; + + // wait for the data to be done transmission before updating, + // it may still be sending the latch/reset though, buts OK + SyncWait(); + + // now it is transferring blank area, so it is safe to update dma buffers + uint16_t* pDma = (uint16_t*)_i2sBlock; + uint8_t* pPixelsEnd = _pixels + _sizePixels; + for (uint8_t* pPixel = _pixels; pPixel < pPixelsEnd; pPixel++) + { + *(pDma++) = bitpatterns[((*pPixel) & 0x0f)]; + *(pDma++) = bitpatterns[((*pPixel) >> 4) & 0x0f]; + } + } + + void StopDma() + { + SET_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_STOP); + } + + void StartDma() + { + // clear all interrupt flags + SET_PERI_REG_MASK(SLC_INT_CLR, 0xffffffff); + + // configure the first descriptor + // TX_LINK isnt used, but has to be configured + CLEAR_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_DESCADDR_MASK); + CLEAR_PERI_REG_MASK(SLC_TX_LINK, SLC_TXLINK_DESCADDR_MASK); + SET_PERI_REG_MASK(SLC_RX_LINK, ((uint32)&_i2sBufDescOut) & SLC_RXLINK_DESCADDR_MASK); + SET_PERI_REG_MASK(SLC_TX_LINK, ((uint32)&_i2sBufDescOut) & SLC_TXLINK_DESCADDR_MASK); + + WRITE_PERI_REG(SLC_INT_CLR, 0xffffffff); + + // start RX link + SET_PERI_REG_MASK(SLC_RX_LINK, SLC_RXLINK_START); + + // fire the machine again + SET_PERI_REG_MASK(I2SCONF, I2S_I2S_TX_START); + } + + void NullWait() const + { + while (!IsStoppedDmaState()) + { + // due to how long the data send could be (300 pixels is 9ms) + // we will yield while we wait + yield(); + } + + // okay right now we are not sending anything + } + + void SyncWait() const + { + // poll for SLC_RX_EOF_DES_ADDR getting set to anything other than + // the buffer with pixel data + + while (READ_PERI_REG(SLC_RX_EOF_DES_ADDR) == (uint32_t)&_i2sBufDescLatch) + { + WRITE_PERI_REG(SLC_RX_EOF_DES_ADDR, 0xffffffff); + // due to how long the data send could be (300 pixels is 9ms) + // we will yield while we wait + yield(); + } + + // okay right now we are somewhere in the blank "latch/reset" section or + // stopped + } + + bool IsStoppedDmaState() const + { + uint32_t state = READ_PERI_REG(SLC_RX_EOF_DES_ADDR); + return (state == 0 || state == (uint32_t)&_i2sBufDescStopped); + } + + size_t _sizePixels; // Size of '_pixels' buffer below + uint8_t* _pixels; // Holds LED color values + + struct slc_queue_item _i2sBufDescOut; + struct slc_queue_item _i2sBufDescLatch; + struct slc_queue_item _i2sBufDescStopped; + + uint32_t _bitBufferSize; + uint8_t* _i2sBlock; + uint8_t _i2sZeroes[24]; // 24 bytes creates the minimum 50us latch per spec +}; + +typedef NeoEsp8266DmaMethodBase NeoEsp8266Dma800KbpsMethod; +typedef NeoEsp8266DmaMethodBase NeoEsp8266Dma400KbpsMethod; + +// Dma method is the default method for Esp8266 +typedef NeoEsp8266Dma800KbpsMethod Neo800KbpsMethod; +typedef NeoEsp8266Dma400KbpsMethod Neo400KbpsMethod; + +#endif \ No newline at end of file diff --git a/src/NeoEsp8266UartMethod.h b/src/NeoEsp8266UartMethod.h new file mode 100644 index 00000000..3c577dfc --- /dev/null +++ b/src/NeoEsp8266UartMethod.h @@ -0,0 +1,137 @@ +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#ifdef ARDUINO_ARCH_ESP8266 + +extern "C" +{ +#include "eagle_soc.h" +#include "uart_register.h" +} + +// due to linker overriding ICACHE_RAM_ATTR for cpp files, this function was +// moved into a NeoPixelEsp8266.c file. +extern "C" void ICACHE_RAM_ATTR esp8266_uart1_send_pixels(uint8_t* pixels, uint8_t* end); + +class NeoEsp8266UartSpeed800Kbps +{ +public: + static const uint32_t ByteSendTimeUs = 10; // us it takes to send a single pixel element at 800mhz speed + static const uint32_t UartBaud = 3200000; // 800mhz, 4 serial bytes per NeoByte +}; + +class NeoEsp8266UartSpeed400Kbps +{ +public: + static const uint32_t ByteSendTimeUs = 20; // us it takes to send a single pixel element at 400mhz speed + static const uint32_t UartBaud = 1600000; // 400mhz, 4 serial bytes per NeoByte +}; + +#define UART1 1 +#define UART1_INV_MASK (0x3f << 19) + +template class NeoEsp8266UartMethodBase +{ +public: + NeoEsp8266UartMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) + { + _sizePixels = pixelCount * elementSize; + _pixels = (uint8_t*)malloc(_sizePixels); + memset(_pixels, 0x00, _sizePixels); + } + + ~NeoEsp8266UartMethodBase() + { + free(_pixels); + } + + bool IsReadyToUpdate() const + { + uint32_t delta = micros() - _endTime; + + return (delta >= 50L && delta <= (4294967296L - getPixelTime())); + } + + void Initialize() + { + Serial1.begin(T_SPEED::UartBaud, SERIAL_6N1, SERIAL_TX_ONLY); + + CLEAR_PERI_REG_MASK(UART_CONF0(UART1), UART1_INV_MASK); + SET_PERI_REG_MASK(UART_CONF0(UART1), (BIT(22))); + + _endTime = micros(); + } + + void Update() + { + // 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 (!IsReadyToUpdate()) + { + yield(); + } + + // since uart is async buffer send, we have to calc the endtime that it will take + // to correctly manage the data latch in the above code + // add the calculated time to the current time + _endTime = micros() + getPixelTime(); + + // esp hardware uart sending of data + esp8266_uart1_send_pixels(_pixels, _pixels + _sizePixels); + } + + uint8_t* getPixels() const + { + return _pixels; + }; + + size_t getPixelsSize() const + { + return _sizePixels; + }; + +private: + uint32_t getPixelTime() const + { + return (T_SPEED::ByteSendTimeUs * _sizePixels); + }; + + size_t _sizePixels; // Size of '_pixels' buffer below + uint8_t* _pixels; // Holds LED color values + uint32_t _endTime; // Latch timing reference +}; + +typedef NeoEsp8266UartMethodBase NeoEsp8266Uart800KbpsMethod; +typedef NeoEsp8266UartMethodBase NeoEsp8266Uart400KbpsMethod; + +#endif \ No newline at end of file diff --git a/src/NeoPixelAnimator.cpp b/src/NeoPixelAnimator.cpp new file mode 100644 index 00000000..74552c25 --- /dev/null +++ b/src/NeoPixelAnimator.cpp @@ -0,0 +1,164 @@ +/*------------------------------------------------------------------------- +NeoPixelAnimator provides animation timing support. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include "NeoPixelAnimator.h" + +NeoPixelAnimator::NeoPixelAnimator(uint16_t countAnimations, uint16_t timeScale) : + _countAnimations(countAnimations), + _animationLastTick(0), + _activeAnimations(0), + _isRunning(true) +{ + // due to strange esp include header issues, min and max aren't usable + _timeScale = (timeScale < 1) ? (1) : (timeScale > 32768) ? 32768 : timeScale; + //_timeScale = max(1, min(32768, timeScale)); + + _animations = new AnimationContext[_countAnimations]; +} + +NeoPixelAnimator::~NeoPixelAnimator() +{ + delete[] _animations; +} + +bool NeoPixelAnimator::NextAvailableAnimation(uint16_t* indexAvailable, uint16_t indexStart) +{ + if (indexStart >= _countAnimations) + { + // last one + indexStart = _countAnimations - 1; + } + + uint16_t next = indexStart; + + do + { + if (!IsAnimationActive(next)) + { + if (indexAvailable) + { + *indexAvailable = next; + } + return true; + } + next = (next + 1) % _countAnimations; + } while (next != indexStart); + return false; +} + +void NeoPixelAnimator::StartAnimation(uint16_t indexAnimation, + uint16_t duration, + AnimUpdateCallback animUpdate) +{ + if (indexAnimation >= _countAnimations || animUpdate == NULL) + { + return; + } + + if (_activeAnimations == 0) + { + _animationLastTick = millis(); + } + + StopAnimation(indexAnimation); + + // all animations must have at least non zero duration, otherwise + // they are considered stopped + if (duration == 0) + { + duration = 1; + } + + _animations[indexAnimation].StartAnimation(duration, animUpdate); + + _activeAnimations++; +} + +void NeoPixelAnimator::StopAnimation(uint16_t indexAnimation) +{ + if (indexAnimation >= _countAnimations) + { + return; + } + + if (IsAnimationActive(indexAnimation)) + { + _activeAnimations--; + _animations[indexAnimation].StopAnimation(); + } +} + +void NeoPixelAnimator::UpdateAnimations() +{ + if (_isRunning) + { + uint32_t currentTick = millis(); + uint32_t delta = currentTick - _animationLastTick; + + if (delta >= _timeScale) + { + uint16_t countAnimations = _activeAnimations; + AnimationContext* pAnim; + + delta /= _timeScale; // scale delta into animation time + + for (uint16_t iAnim = 0; iAnim < _countAnimations && countAnimations > 0; iAnim++) + { + pAnim = &_animations[iAnim]; + AnimUpdateCallback fnUpdate = pAnim->_fnCallback; + AnimationParam param; + + param.index = iAnim; + + if (pAnim->_remaining > delta) + { + param.state = (pAnim->_remaining == pAnim->_duration) ? AnimationState_Started : AnimationState_Progress; + param.progress = (float)(pAnim->_duration - pAnim->_remaining) / (float)pAnim->_duration; + + fnUpdate(param); + + pAnim->_remaining -= delta; + + countAnimations--; + } + else if (pAnim->_remaining > 0) + { + param.state = AnimationState_Completed; + param.progress = 1.0f; + + _activeAnimations--; + pAnim->StopAnimation(); + + fnUpdate(param); + + countAnimations--; + } + } + + _animationLastTick = currentTick; + } + } +} diff --git a/src/NeoPixelAnimator.h b/src/NeoPixelAnimator.h new file mode 100644 index 00000000..84095e6f --- /dev/null +++ b/src/NeoPixelAnimator.h @@ -0,0 +1,167 @@ +/*------------------------------------------------------------------------- +NeoPixelAnimator provides animation timing support. + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#pragma once + +#include +#include "NeoEase.h" + +enum AnimationState +{ + AnimationState_Started, + AnimationState_Progress, + AnimationState_Completed +}; + +struct AnimationParam +{ + float progress; + uint16_t index; + AnimationState state; +}; + +#ifdef ARDUINO_ARCH_AVR + +typedef void(*AnimUpdateCallback)(const AnimationParam& param); + +#else + +#undef max +#undef min +#include +typedef std::function AnimUpdateCallback; + +#endif + + +#define NEO_MILLISECONDS 1 // ~65 seconds max duration, ms updates +#define NEO_CENTISECONDS 10 // ~10.9 minutes max duration, centisecond updates +#define NEO_DECISECONDS 100 // ~1.8 hours max duration, decisecond updates +#define NEO_SECONDS 1000 // ~18.2 hours max duration, second updates +#define NEO_DECASECONDS 10000 // ~7.5 days, 10 second updates + +class NeoPixelAnimator +{ +public: + NeoPixelAnimator(uint16_t countAnimations, uint16_t timeScale = NEO_MILLISECONDS); + ~NeoPixelAnimator(); + + bool IsAnimating() const + { + return _activeAnimations > 0; + } + + + bool NextAvailableAnimation(uint16_t* indexAvailable, uint16_t indexStart = 0); + + void StartAnimation(uint16_t indexAnimation, uint16_t duration, AnimUpdateCallback animUpdate); + void StopAnimation(uint16_t indexAnimation); + void RestartAnimation(uint16_t indexAnimation) + { + if (indexAnimation >= _countAnimations || _animations[indexAnimation]._duration == 0) + { + return; + } + + StartAnimation(indexAnimation, _animations[indexAnimation]._duration, (_animations[indexAnimation]._fnCallback)); + } + + bool IsAnimationActive(uint16_t indexAnimation) const + { + if (indexAnimation >= _countAnimations) + { + return false; + } + return (IsAnimating() && _animations[indexAnimation]._remaining != 0); + } + + uint16_t AnimationDuration(uint16_t indexAnimation) + { + if (indexAnimation >= _countAnimations) + { + return 0; + } + return _animations[indexAnimation]._duration; + } + + void UpdateAnimations(); + + bool IsPaused() + { + return (!_isRunning); + } + + void Pause() + { + _isRunning = false; + } + + void Resume() + { + _isRunning = true; + _animationLastTick = millis(); + } + + uint16_t TimeScale() + { + return _timeScale; + } + +private: + struct AnimationContext + { + AnimationContext() : + _duration(0), + _remaining(0), + _fnCallback(NULL) + {} + + void StartAnimation(uint16_t duration, AnimUpdateCallback animUpdate) + { + _duration = duration; + _remaining = duration; + _fnCallback = animUpdate; + + } + + void StopAnimation() + { + _remaining = 0; + } + + uint16_t _duration; + uint16_t _remaining; + + AnimUpdateCallback _fnCallback; + }; + + uint16_t _countAnimations; + AnimationContext* _animations; + uint32_t _animationLastTick; + uint16_t _activeAnimations; + uint16_t _timeScale; + bool _isRunning; +}; diff --git a/src/NeoPixelAvr.c b/src/NeoPixelAvr.c new file mode 100644 index 00000000..3d057e27 --- /dev/null +++ b/src/NeoPixelAvr.c @@ -0,0 +1,648 @@ +/*------------------------------------------------------------------------- +Arduino library to control a wide variety of WS2811- and WS2812-based RGB +LED devices such as Adafruit FLORA RGB Smart Pixels and NeoPixel strips. +Currently handles 400 and 800 KHz bitstreams on 8, 12 and 16 MHz ATmega +MCUs, with LEDs wired for various color orders. 8 MHz MCUs provide +output on PORTB and PORTD, while 16 MHz chips can handle most output pins +(possible exception with upper PORT registers on the Arduino Mega). + +Written by Phil Burgess / Paint Your Dragon for Adafruit Industries, +contributions by PJRC, Michael Miller and other members of the open +source community. + +Adafruit invests time and resources providing this open source code, +please support Adafruit and open-source hardware by purchasing products +from Adafruit! + +------------------------------------------------------------------------- +The contents of this file were taken from the Adafruit NeoPixel library +and modified only to fit within individual calling functions. + +NeoPixel is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixel is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +// must also check for arm due to Teensy incorrectly having ARDUINO_ARCH_AVR set +#if defined(ARDUINO_ARCH_AVR) && !defined(__arm__) + +#include + +// Hand-tuned assembly code issues data to the LED drivers at a specific +// rate. There's separate code for different CPU speeds (8, 12, 16 MHz) +// for both the WS2811 (400 KHz) and WS2812 (800 KHz) drivers. The +// datastream timing for the LED drivers allows a little wiggle room each +// way (listed in the datasheets), so the conditions for compiling each +// case are set up for a range of frequencies rather than just the exact +// 8, 12 or 16 MHz values, permitting use with some close-but-not-spot-on +// devices (e.g. 16.5 MHz DigiSpark). The ranges were arrived at based +// on the datasheet figures and have not been extensively tested outside +// the canonical 8/12/16 MHz speeds; there's no guarantee these will work +// close to the extremes (or possibly they could be pushed further). +// Keep in mind only one CPU speed case actually gets compiled; the +// resulting program isn't as massive as it might look from source here. + +#if (F_CPU >= 7400000UL) && (F_CPU <= 9500000UL) // 8Mhz CPU + +#ifdef PORTD // PORTD isn't present on ATtiny85, etc. +void send_pixels_8mhz_800_PortD(uint8_t* pixels, size_t sizePixels, uint8_t pinMask) +{ + volatile size_t i = sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + volatile uint8_t n1; + volatile n2 = 0; // First, next bits out + + // Squeezing an 800 KHz stream out of an 8 MHz chip requires code + // specific to each PORT register. At present this is only written + // to work with pins on PORTD or PORTB, the most likely use case -- + // this covers all the pins on the Adafruit Flora and the bulk of + // digital pins on the Arduino Pro 8 MHz (keep in mind, this code + // doesn't even get compiled for 16 MHz boards like the Uno, Mega, + // Leonardo, etc., so don't bother extending this out of hand). + // Additional PORTs could be added if you really need them, just + // duplicate the else and loop and change the PORT. Each add'l + // PORT will require about 150(ish) bytes of program space. + + // 10 instruction clocks per bit: HHxxxxxLLL + // OUT instructions: ^ ^ ^ (T=0,2,7) + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + n1 = lo; + + if (b & 0x80) + { + n1 = hi; + } + + // Dirty trick: RJMPs proceeding to the next instruction are used + // to delay two clock cycles in one instruction word (rather than + // using two NOPs). This was necessary in order to squeeze the + // loop down to exactly 64 words -- the maximum possible for a + // relative branch. + + asm volatile( + "headD:" "\n\t" // Clk Pseudocode + // Bit 7: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 6" "\n\t" // 1-2 if(b & 0x40) + "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 6: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 5" "\n\t" // 1-2 if(b & 0x20) + "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 5: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 4" "\n\t" // 1-2 if(b & 0x10) + "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 4: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 3" "\n\t" // 1-2 if(b & 0x08) + "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 3: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 2" "\n\t" // 1-2 if(b & 0x04) + "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 2: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 1" "\n\t" // 1-2 if(b & 0x02) + "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "rjmp .+0" "\n\t" // 2 nop nop + // Bit 1: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n2] , %[lo]" "\n\t" // 1 n2 = lo + "out %[port] , %[n1]" "\n\t" // 1 PORT = n1 + "rjmp .+0" "\n\t" // 2 nop nop + "sbrc %[byte] , 0" "\n\t" // 1-2 if(b & 0x01) + "mov %[n2] , %[hi]" "\n\t" // 0-1 n2 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "sbiw %[count], 1" "\n\t" // 2 i-- (don't act on Z flag yet) + // Bit 0: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi + "mov %[n1] , %[lo]" "\n\t" // 1 n1 = lo + "out %[port] , %[n2]" "\n\t" // 1 PORT = n2 + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ + "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) + "mov %[n1] , %[hi]" "\n\t" // 0-1 n1 = hi + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo + "brne headD" "\n" // 2 while(i) (Z flag set above) + : [byte] "+r" (b), + [n1] "+r" (n1), + [n2] "+r" (n2), + [count] "+w" (i) + : [port] "I" (_SFR_IO_ADDR(PORTD)), + [ptr] "e" (ptr), + [hi] "r" (hi), + [lo] "r" (lo) ); +} +#endif + +void send_pixels_8mhz_800_PortB(uint8_t* pixels, size_t sizePixels, uint8_t pinMask) +{ + volatile size_t i = sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + volatile uint8_t n1; + volatile n2 = 0; // First, next bits out + + // Same as above, just switched to PORTB and stripped of comments. + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + n1 = lo; + if (b & 0x80) + { + n1 = hi; + } + + asm volatile( + "headB:" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n2] , %[lo]" "\n\t" + "out %[port] , %[n1]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 6" "\n\t" + "mov %[n2] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n1] , %[lo]" "\n\t" + "out %[port] , %[n2]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 5" "\n\t" + "mov %[n1] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n2] , %[lo]" "\n\t" + "out %[port] , %[n1]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 4" "\n\t" + "mov %[n2] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n1] , %[lo]" "\n\t" + "out %[port] , %[n2]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 3" "\n\t" + "mov %[n1] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n2] , %[lo]" "\n\t" + "out %[port] , %[n1]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 2" "\n\t" + "mov %[n2] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n1] , %[lo]" "\n\t" + "out %[port] , %[n2]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 1" "\n\t" + "mov %[n1] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "rjmp .+0" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n2] , %[lo]" "\n\t" + "out %[port] , %[n1]" "\n\t" + "rjmp .+0" "\n\t" + "sbrc %[byte] , 0" "\n\t" + "mov %[n2] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "sbiw %[count], 1" "\n\t" + "out %[port] , %[hi]" "\n\t" + "mov %[n1] , %[lo]" "\n\t" + "out %[port] , %[n2]" "\n\t" + "ld %[byte] , %a[ptr]+" "\n\t" + "sbrc %[byte] , 7" "\n\t" + "mov %[n1] , %[hi]" "\n\t" + "out %[port] , %[lo]" "\n\t" + "brne headB" "\n" + : [byte] "+r" (b), [n1] "+r" (n1), [n2] "+r" (n2), [count] "+w" (i) + : [port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), + [lo] "r" (lo)); +} + +void send_pixels_8mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask) +{ + volatile size_t i = sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + // Timing is more relaxed; unrolling the inner loop for each bit is + // not necessary. Still using the peculiar RJMPs as 2X NOPs, not out + // of need but just to trim the code size down a little. + // This 400-KHz-datastream-on-8-MHz-CPU code is not quite identical + // to the 800-on-16 code later -- the hi/lo timing between WS2811 and + // WS2812 is not simply a 2:1 scale! + + // 20 inst. clocks per bit: HHHHxxxxxxLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile( + "head20:" "\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 & 128) + "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) + "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 6) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) + "dec %[bit]" "\n\t" // 1 bit-- (T = 8) + "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) + "rjmp .+0" "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" "\n\t" // 2 nop nop (T = 18) + "rjmp head20" "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" "\n\t" // (T = 10) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 12) + "nop" "\n\t" // 1 nop (T = 13) + "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 14) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 16) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) + "brne head20" "\n" // 2 if(i != 0) -> (next byte) + : [port] "+e" (port), + [byte] "+r" (b), + [bit] "+r" (bit), + [next] "+r" (next), + [count] "+w" (i) + : [hi] "r" (hi), + [lo] "r" (lo), + [ptr] "e" (ptr)); +} + +#elif (F_CPU >= 11100000UL) && (F_CPU <= 14300000UL) // 12Mhz CPU + +#ifdef PORTD // PORTD isn't present on ATtiny85, etc. +void send_pixels_12mhz_800_PortD(uint8_t* pixels, size_t sizePixels, uint8_t pinMask) +{ + volatile size_t i = sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + // In the 12 MHz case, an optimized 800 KHz datastream (no dead time + // between bytes) requires a PORT-specific loop similar to the 8 MHz + // code (but a little more relaxed in this case). + + // 15 instruction clocks per bit: HHHHxxxxxxLLLLL + // OUT instructions: ^ ^ ^ (T=0,4,10) + + volatile uint8_t next; + + hi = PORTD | pinMask; + lo = PORTD & ~pinMask; + next = lo; + if (b & 0x80) next = hi; + + // Don't "optimize" the OUT calls into the bitTime subroutine; + // we're exploiting the RCALL and RET as 3- and 4-cycle NOPs! + asm volatile( + "headD:" "\n\t" // (T = 0) + "out %[port], %[hi]" "\n\t" // (T = 1) + "rcall bitTimeD" "\n\t" // Bit 7 (T = 15) + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 6 + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 5 + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 4 + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 3 + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 2 + "out %[port], %[hi]" "\n\t" + "rcall bitTimeD" "\n\t" // Bit 1 + // Bit 0: + "out %[port] , %[hi]" "\n\t" // 1 PORT = hi (T = 1) + "rjmp .+0" "\n\t" // 2 nop nop (T = 3) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 5) + "out %[port] , %[next]" "\n\t" // 1 PORT = next (T = 6) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 7) + "sbrc %[byte] , 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 9) + "nop" "\n\t" // 1 (T = 10) + "out %[port] , %[lo]" "\n\t" // 1 PORT = lo (T = 11) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 13) + "brne headD" "\n\t" // 2 if(i != 0) -> (next byte) + "rjmp doneD" "\n\t" + "bitTimeD:" "\n\t" // nop nop nop (T = 4) + "out %[port], %[next]" "\n\t" // 1 PORT = next (T = 5) + "mov %[next], %[lo]" "\n\t" // 1 next = lo (T = 6) + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 7) + "sbrc %[byte], 7" "\n\t" // 1-2 if(b & 0x80) (T = 8) + "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 9) + "nop" "\n\t" // 1 (T = 10) + "out %[port], %[lo]" "\n\t" // 1 PORT = lo (T = 11) + "ret" "\n\t" // 4 nop nop nop nop (T = 15) + "doneD:" "\n" + : [byte] "+r" (b), + [next] "+r" (next), + [count] "+w" (i) + : [port] "I" (_SFR_IO_ADDR(PORTD)), + [ptr] "e" (ptr), + [hi] "r" (hi), + [lo] "r" (lo)); +} +#endif + +void send_pixels_12mhz_800_PortB(uint8_t* pixels, size_t sizePixels, uint8_t pinMask) +{ + volatile uint16_t i = (uint16_t)sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + volatile uint8_t next; + + hi = PORTB | pinMask; + lo = PORTB & ~pinMask; + next = lo; + if (b & 0x80) + { + next = hi; + } + + // Same as above, just set for PORTB & stripped of comments + asm volatile( + "headB:" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port], %[hi]" "\n\t" + "rcall bitTimeB" "\n\t" + "out %[port] , %[hi]" "\n\t" + "rjmp .+0" "\n\t" + "ld %[byte] , %a[ptr]+" "\n\t" + "out %[port] , %[next]" "\n\t" + "mov %[next] , %[lo]" "\n\t" + "sbrc %[byte] , 7" "\n\t" + "mov %[next] , %[hi]" "\n\t" + "nop" "\n\t" + "out %[port] , %[lo]" "\n\t" + "sbiw %[count], 1" "\n\t" + "brne headB" "\n\t" + "rjmp doneB" "\n\t" + "bitTimeB:" "\n\t" + "out %[port], %[next]" "\n\t" + "mov %[next], %[lo]" "\n\t" + "rol %[byte]" "\n\t" + "sbrc %[byte], 7" "\n\t" + "mov %[next], %[hi]" "\n\t" + "nop" "\n\t" + "out %[port], %[lo]" "\n\t" + "ret" "\n\t" + "doneB:" "\n" + : [byte] "+r" (b), [next] "+r" (next), [count] "+w" (i) + : [port] "I" (_SFR_IO_ADDR(PORTB)), [ptr] "e" (ptr), [hi] "r" (hi), + [lo] "r" (lo)); +} + +void send_pixels_12mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask) +{ + volatile uint16_t i = (uint16_t)sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + // 30 instruction clocks per bit: HHHHHHxxxxxxxxxLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,6,15) + + volatile uint8_t next, bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile( + "head30:" "\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 & 128) + "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) + "rjmp .+0" "\n\t" // 2 nop nop (T = 10) + "rjmp .+0" "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" "\n\t" // 2 nop nop (T = 14) + "nop" "\n\t" // 1 nop (T = 15) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 17) + "rjmp .+0" "\n\t" // 2 nop nop (T = 19) + "dec %[bit]" "\n\t" // 1 bit-- (T = 20) + "breq nextbyte30" "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 22) + "rjmp .+0" "\n\t" // 2 nop nop (T = 24) + "rjmp .+0" "\n\t" // 2 nop nop (T = 26) + "rjmp .+0" "\n\t" // 2 nop nop (T = 28) + "rjmp head30" "\n\t" // 2 -> head30 (next bit out) + "nextbyte30:" "\n\t" // (T = 22) + "nop" "\n\t" // 1 nop (T = 23) + "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 24) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 26) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 28) + "brne head30" "\n" // 1-2 if(i != 0) -> (next byte) + : [port] "+e" (port), + [byte] "+r" (b), + [bit] "+r" (bit), + [next] "+r" (next), + [count] "+w" (i) + : [hi] "r" (hi), + [lo] "r" (lo), + [ptr] "e" (ptr)); +} + +#elif (F_CPU >= 15400000UL) && (F_CPU <= 19000000L) // 16Mhz CPU + +void send_pixels_16mhz_800(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask) +{ + volatile uint16_t i = (uint16_t)sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 + + // WS2811 and WS2812 have different hi/lo duty cycles; this is + // similar but NOT an exact copy of the prior 400-on-8 code. + + // 20 inst. clocks per bit: HHHHHxxxxxxxxLLLLLLL + // ST instructions: ^ ^ ^ (T=0,5,13) + + volatile uint8_t next; + volatile uint8_t bit; + + hi = *port | pinMask; + lo = *port & ~pinMask; + next = lo; + bit = 8; + + asm volatile( + "head20:" "\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 & 128) + "mov %[next], %[hi]" "\n\t" // 0-1 next = hi (T = 4) + "dec %[bit]" "\n\t" // 1 bit-- (T = 5) + "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 7) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 8) + "breq nextbyte20" "\n\t" // 1-2 if(bit == 0) (from dec above) + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 10) + "rjmp .+0" "\n\t" // 2 nop nop (T = 12) + "nop" "\n\t" // 1 nop (T = 13) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) + "nop" "\n\t" // 1 nop (T = 16) + "rjmp .+0" "\n\t" // 2 nop nop (T = 18) + "rjmp head20" "\n\t" // 2 -> head20 (next bit out) + "nextbyte20:" "\n\t" // (T = 10) + "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 11) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 13) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 15) + "nop" "\n\t" // 1 nop (T = 16) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 18) + "brne head20" "\n" // 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)); +} + +void send_pixels_16mhz_400(uint8_t* pixels, size_t sizePixels, volatile uint8_t* port, uint8_t pinMask) +{ + volatile size_t i = sizePixels; // Loop counter + volatile uint8_t* ptr = pixels; // 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 400 KHz clock on 16 MHz MCU is the most 'relaxed' version. + + // 40 inst. clocks per bit: HHHHHHHHxxxxxxxxxxxxLLLLLLLLLLLLLLLLLLLL + // ST instructions: ^ ^ ^ (T=0,8,20) + + volatile uint8_t next, 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 & 128) + "mov %[next] , %[hi]" "\n\t" // 0-1 next = hi (T = 4) + "rjmp .+0" "\n\t" // 2 nop nop (T = 6) + "rjmp .+0" "\n\t" // 2 nop nop (T = 8) + "st %a[port], %[next]" "\n\t" // 2 PORT = next (T = 10) + "rjmp .+0" "\n\t" // 2 nop nop (T = 12) + "rjmp .+0" "\n\t" // 2 nop nop (T = 14) + "rjmp .+0" "\n\t" // 2 nop nop (T = 16) + "rjmp .+0" "\n\t" // 2 nop nop (T = 18) + "rjmp .+0" "\n\t" // 2 nop nop (T = 20) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 22) + "nop" "\n\t" // 1 nop (T = 23) + "mov %[next] , %[lo]" "\n\t" // 1 next = lo (T = 24) + "dec %[bit]" "\n\t" // 1 bit-- (T = 25) + "breq nextbyte40" "\n\t" // 1-2 if(bit == 0) + "rol %[byte]" "\n\t" // 1 b <<= 1 (T = 27) + "nop" "\n\t" // 1 nop (T = 28) + "rjmp .+0" "\n\t" // 2 nop nop (T = 30) + "rjmp .+0" "\n\t" // 2 nop nop (T = 32) + "rjmp .+0" "\n\t" // 2 nop nop (T = 34) + "rjmp .+0" "\n\t" // 2 nop nop (T = 36) + "rjmp .+0" "\n\t" // 2 nop nop (T = 38) + "rjmp head40" "\n\t" // 2 -> head40 (next bit out) + "nextbyte40:" "\n\t" // (T = 27) + "ldi %[bit] , 8" "\n\t" // 1 bit = 8 (T = 28) + "ld %[byte] , %a[ptr]+" "\n\t" // 2 b = *ptr++ (T = 30) + "rjmp .+0" "\n\t" // 2 nop nop (T = 32) + "st %a[port], %[lo]" "\n\t" // 2 PORT = lo (T = 34) + "rjmp .+0" "\n\t" // 2 nop nop (T = 36) + "sbiw %[count], 1" "\n\t" // 2 i-- (T = 38) + "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)); +} + +#else +#error "CPU SPEED NOT SUPPORTED" +#endif + +#endif \ No newline at end of file diff --git a/src/NeoPixelBus.h b/src/NeoPixelBus.h new file mode 100644 index 00000000..d8532b83 --- /dev/null +++ b/src/NeoPixelBus.h @@ -0,0 +1,165 @@ +/*------------------------------------------------------------------------- +NeoPixel library + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +#include + +#include "RgbColor.h" +#include "HslColor.h" +#include "HsbColor.h" +#include "RgbwColor.h" +#include "NeoColorFeatures.h" + +#if defined(ARDUINO_ARCH_ESP8266) +#include "NeoEsp8266DmaMethod.h" +#include "NeoEsp8266UartMethod.h" +#include "NeoEsp8266BitBangMethod.h" +#elif defined(__arm__) // must be before ARDUINO_ARCH_AVR due to Teensy incorrectly having it set +#include "NeoArmMethod.h" +#elif defined(ARDUINO_ARCH_AVR) +#include "NeoAvrMethod.h" +#else +#error "Platform Currently Not Supported, please add an Issue at Github/Makuna/NeoPixelBus" +#endif + +// '_state' flags for internal state +#define NEO_DIRTY 0x80 // a change was made to pixel data that requires a show + +template class NeoPixelBus +{ +public: + // Constructor: number of LEDs, pin number + // NOTE: Pin Number maybe ignored due to hardware limitations of the method. + + NeoPixelBus(uint16_t countPixels, uint8_t pin) : + _countPixels(countPixels), + _method(pin, countPixels, T_COLOR_FEATURE::PixelSize) + { + } + + ~NeoPixelBus() + { + + } + + void Begin() + { + _method.Initialize(); + Dirty(); + } + + void Show() + { + if (!IsDirty()) + { + return; + } + + _method.Update(); + + ResetDirty(); + } + + inline bool CanShow() const + { + return _method.IsReadyToUpdate(); + }; + + bool IsDirty() const + { + return (_state & NEO_DIRTY); + }; + + void Dirty() + { + _state |= NEO_DIRTY; + }; + + void ResetDirty() + { + _state &= ~NEO_DIRTY; + }; + + uint8_t* Pixels() const + { + return _method.getPixels(); + }; + + size_t PixelsSize() const + { + return _method.getPixelsSize(); + }; + + size_t PixelSize() const + { + return T_COLOR_FEATURE::PixelSize; + }; + + uint16_t PixelCount() const + { + return _countPixels; + }; + + void SetPixelColor(uint16_t indexPixel, typename T_COLOR_FEATURE::ColorObject color) + { + if (indexPixel < _countPixels) + { + T_COLOR_FEATURE::applyPixelColor(_method.getPixels(), indexPixel, color); + Dirty(); + } + }; + + typename T_COLOR_FEATURE::ColorObject GetPixelColor(uint16_t indexPixel) const + { + if (indexPixel < _countPixels) + { + return T_COLOR_FEATURE::retrievePixelColor(_method.getPixels(), indexPixel); + } + else + { + // Pixel # is out of bounds, this will get converted to a + // color object type initialized to 0 (black) + return 0; + } + }; + + void ClearTo(typename T_COLOR_FEATURE::ColorObject color) + { + uint8_t* pixels = _method.getPixels(); + for (uint16_t n = 0; n < _countPixels; n++) + { + T_COLOR_FEATURE::applyPixelColor(pixels, n, color); + } + Dirty(); + }; + +private: + const uint16_t _countPixels; // Number of RGB LEDs in strip + + uint8_t _state; // internal state + T_METHOD _method; +}; + diff --git a/NeoPixelesp8266.c b/src/NeoPixelEsp8266.c similarity index 59% rename from NeoPixelesp8266.c rename to src/NeoPixelEsp8266.c index 652a3c0d..0ba4e827 100644 --- a/NeoPixelesp8266.c +++ b/src/NeoPixelEsp8266.c @@ -1,27 +1,59 @@ -/* -NeoPixelEsp8266.h - NeoPixel library helper functions for Esp8266 using cycle count -Copyright (c) 2015 Michael C. Miller. All right reserved. +/*------------------------------------------------------------------------- +NeoPixel library helper functions for Esp8266. -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. +Written by Michael C. Miller. -This library is distributed in the hope that it will be useful, +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -*/ +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ -#if defined(ESP8266) +#ifdef ARDUINO_ARCH_ESP8266 #include #include +void ICACHE_RAM_ATTR esp8266_uart1_send_pixels(uint8_t* pixels, uint8_t* end) +{ + const uint8_t _uartData[4] = { 0b00110111, 0b00000111, 0b00110100, 0b00000100 }; + const uint8_t _uartFifoTrigger = 124; // tx fifo should be 128 bytes. minus the four we need to send + + do + { + uint8_t subpix = *pixels++; + uint8_t buf[4] = { _uartData[(subpix >> 6) & 3], + _uartData[(subpix >> 4) & 3], + _uartData[(subpix >> 2) & 3], + _uartData[subpix & 3] }; + + // now wait till this the FIFO buffer has room to send more + while (((U1S >> USTXC) & 0xff) > _uartFifoTrigger); + + for (uint8_t i = 0; i < 4; i++) + { + // directly write the byte to transfer into the UART1 FIFO register + U1F = buf[i]; + } + + } while (pixels < end); +} + inline uint32_t _getCycleCount() { uint32_t ccount; @@ -36,7 +68,7 @@ inline uint32_t _getCycleCount() #define CYCLES_400_T1H (F_CPU / 833333) #define CYCLES_400 (F_CPU / 400000) -void ICACHE_RAM_ATTR send_pixels_800(uint8_t* pixels, uint8_t* end, uint8_t pin) +void ICACHE_RAM_ATTR bitbang_send_pixels_800(uint8_t* pixels, uint8_t* end, uint8_t pin) { const uint32_t pinRegister = _BV(pin); uint8_t mask; @@ -77,13 +109,9 @@ void ICACHE_RAM_ATTR send_pixels_800(uint8_t* pixels, uint8_t* end, uint8_t pin) GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinRegister); } } while (pixels < end); - - // while accurate, this isn't needed due to the delays at the - // top of Show() to enforce between update timing - // while ((_getCycleCount() - cyclesStart) < CYCLES_800); } -void ICACHE_RAM_ATTR send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin) +void ICACHE_RAM_ATTR bitbang_send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin) { const uint32_t pinRegister = _BV(pin); uint8_t mask; @@ -92,7 +120,7 @@ void ICACHE_RAM_ATTR send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin) // trigger emediately cyclesStart = _getCycleCount() - CYCLES_400; - while (pixels < end) + do { subpix = *pixels++; for (mask = 0x80; mask; mask >>= 1) @@ -121,11 +149,7 @@ void ICACHE_RAM_ATTR send_pixels_400(uint8_t* pixels, uint8_t* end, uint8_t pin) // set low GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, pinRegister); } - } - - // while accurate, this isn't needed due to the delays at the - // top of Show() to enforce between update timing - // while ((_getCycleCount() - cyclesStart) < CYCLES_400); + } while (pixels < end); } -#endif \ No newline at end of file +#endif diff --git a/src/RgbColor.cpp b/src/RgbColor.cpp new file mode 100644 index 00000000..da932403 --- /dev/null +++ b/src/RgbColor.cpp @@ -0,0 +1,215 @@ +/*------------------------------------------------------------------------- +RgbColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include "RgbColor.h" +#include "HslColor.h" +#include "HsbColor.h" + +static float _CalcColor(float p, float q, float t) +{ + if (t < 0.0f) + t += 1.0f; + if (t > 1.0f) + t -= 1.0f; + + if (t < 1.0f / 6.0f) + return p + (q - p) * 6.0f * t; + + if (t < 0.5f) + return q; + + if (t < 2.0f / 3.0f) + return p + ((q - p) * (2.0f / 3.0f - t) * 6.0f); + + return p; +} + +RgbColor::RgbColor(HslColor color) +{ + float r; + float g; + float b; + + float h = color.H; + float s = color.S; + float l = color.L; + + + if (color.S == 0.0f || color.L == 0.0f) + { + r = g = b = l; // achromatic or black + } + else + { + float q = l < 0.5f ? l * (1.0f + s) : l + s - (l * s); + float p = 2.0f * l - q; + r = _CalcColor(p, q, h + 1.0f / 3.0f); + g = _CalcColor(p, q, h); + b = _CalcColor(p, q, h - 1.0f / 3.0f); + } + + R = (uint8_t)(r * 255.0f); + G = (uint8_t)(g * 255.0f); + B = (uint8_t)(b * 255.0f); +} + +RgbColor::RgbColor(HsbColor color) +{ + float r; + float g; + float b; + + float h = color.H; + float s = color.S; + float v = color.B; + + if (color.S == 0.0f) + { + r = g = b = v; // achromatic or black + } + else + { + if (h < 0.0f) + h += 1.0f; + if (h > 1.0f) + h -= 1.0f; + h *= 6.0f; + int i = (int)h; + float f = h - i; + float q = v * (1.0f - s * f); + float p = v * (1.0f - s); + float t = v * (1.0f - s * (1.0f - f)); + switch (i) + { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + default: + r = v; + g = p; + b = q; + break; + } + } + + R = (uint8_t)(r * 255.0f); + G = (uint8_t)(g * 255.0f); + B = (uint8_t)(b * 255.0f); +} + +uint8_t RgbColor::CalculateBrightness() const +{ + return (uint8_t)(((uint16_t)R + (uint16_t)G + (uint16_t)B) / 3); +} + +void RgbColor::Darken(uint8_t delta) +{ + if (R > delta) + { + R -= delta; + } + else + { + R = 0; + } + + if (G > delta) + { + G -= delta; + } + else + { + G = 0; + } + + if (B > delta) + { + B -= delta; + } + else + { + B = 0; + } +} + +void RgbColor::Lighten(uint8_t delta) +{ + if (R < 255 - delta) + { + R += delta; + } + else + { + R = 255; + } + + if (G < 255 - delta) + { + G += delta; + } + else + { + G = 255; + } + + if (B < 255 - delta) + { + B += delta; + } + else + { + B = 255; + } +} + +RgbColor RgbColor::LinearBlend(RgbColor left, RgbColor right, float progress) +{ + return RgbColor( left.R + ((right.R - left.R) * progress), + left.G + ((right.G - left.G) * progress), + left.B + ((right.B - left.B) * progress)); +} \ No newline at end of file diff --git a/RgbColor.h b/src/RgbColor.h similarity index 68% rename from RgbColor.h rename to src/RgbColor.h index fc3ca1f5..1852c108 100644 --- a/RgbColor.h +++ b/src/RgbColor.h @@ -1,10 +1,20 @@ -/*-------------------------------------------------------------------- -NeoPixel is free software: you can redistribute it and/or modify +/*------------------------------------------------------------------------- +RgbColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. -NeoPixel is distributed in the hope that it will be useful, +NeoPixelBus is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. @@ -12,11 +22,14 @@ GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with NeoPixel. If not, see . ---------------------------------------------------------------------*/ +-------------------------------------------------------------------------*/ #pragma once #include +struct HslColor; +struct HsbColor; + // ------------------------------------------------------------------------ // RgbColor represents a color object that is represented by Red, Green, Blue // component values. It contains helpful color routines to manipulate the @@ -27,64 +40,74 @@ struct RgbColor // ------------------------------------------------------------------------ // Construct a RgbColor using R, G, B values (0-255) // ------------------------------------------------------------------------ - RgbColor(uint8_t r, uint8_t g, uint8_t b) : - R(r), G(g), B(b) - { - }; + RgbColor(uint8_t r, uint8_t g, uint8_t b) : + R(r), G(g), B(b) + { + }; // ------------------------------------------------------------------------ // Construct a RgbColor using a single brightness value (0-255) // This works well for creating gray tone colors - // (0) = blakc, (255) = white, (128) = gray + // (0) = black, (255) = white, (128) = gray + // ------------------------------------------------------------------------ + RgbColor(uint8_t brightness) : + R(brightness), G(brightness), B(brightness) + { + }; + + // ------------------------------------------------------------------------ + // Construct a RgbColor using HslColor + // ------------------------------------------------------------------------ + RgbColor(HslColor color); + + // ------------------------------------------------------------------------ + // Construct a RgbColor using HsbColor // ------------------------------------------------------------------------ - RgbColor(uint8_t brightness) : - R(brightness), G(brightness), B(brightness) - { - }; + RgbColor(HsbColor color); // ------------------------------------------------------------------------ // Construct a RgbColor that will have its values set in latter operations // CAUTION: The R,G,B members are not initialized and may not be consistent // ------------------------------------------------------------------------ - RgbColor() - { - }; + RgbColor() + { + }; // ------------------------------------------------------------------------ // CalculateBrightness will calculate the overall brightness // NOTE: This is a simple linear brightness // ------------------------------------------------------------------------ - uint8_t CalculateBrightness(); + uint8_t CalculateBrightness() const; // ------------------------------------------------------------------------ // Darken will adjust the color by the given delta toward black // NOTE: This is a simple linear change // delta - (0-255) the amount to dim the color // ------------------------------------------------------------------------ - void Darken(uint8_t delta); + void Darken(uint8_t delta); // ------------------------------------------------------------------------ // Lighten will adjust the color by the given delta toward white // NOTE: This is a simple linear change // delta - (0-255) the amount to lighten the color // ------------------------------------------------------------------------ - void Lighten(uint8_t delta); + void Lighten(uint8_t delta); // ------------------------------------------------------------------------ // LinearBlend between two colors by the amount defined by progress variable // left - the color to start the blend at // right - the color to end the blend at - // progress - (0-255) value where 0 will return left and 255 will return right + // progress - (0.0 - 1.0) value where 0 will return left and 1.0 will return right // and a value between will blend the color weighted linearly between them // ------------------------------------------------------------------------ - static RgbColor LinearBlend(RgbColor left, RgbColor right, uint8_t progress); + static RgbColor LinearBlend(RgbColor left, RgbColor right, float progress); // ------------------------------------------------------------------------ // Red, Green, Blue color members (0-255) where // (0,0,0) is black and (255,255,255) is white // ------------------------------------------------------------------------ - uint8_t R; - uint8_t G; - uint8_t B; + uint8_t R; + uint8_t G; + uint8_t B; }; diff --git a/src/RgbwColor.cpp b/src/RgbwColor.cpp new file mode 100644 index 00000000..e4abdae6 --- /dev/null +++ b/src/RgbwColor.cpp @@ -0,0 +1,146 @@ +/*------------------------------------------------------------------------- +RgbwColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ + +#include "RgbColor.h" +#include "HslColor.h" +#include "HsbColor.h" +#include "RgbwColor.h" + +RgbwColor::RgbwColor(HslColor color) +{ + RgbColor rgbColor(color); + *this = rgbColor; +} + +RgbwColor::RgbwColor(HsbColor color) +{ + RgbColor rgbColor(color); + *this = rgbColor; +} + +uint8_t RgbwColor::CalculateBrightness() const +{ + uint8_t colorB = (uint8_t)(((uint16_t)R + (uint16_t)G + (uint16_t)B) / 3); + if (W > colorB) + { + return W; + } + else + { + return colorB; + } +} + +void RgbwColor::Darken(uint8_t delta) +{ + if (R > delta) + { + R -= delta; + } + else + { + R = 0; + } + + if (G > delta) + { + G -= delta; + } + else + { + G = 0; + } + + if (B > delta) + { + B -= delta; + } + else + { + B = 0; + } + + if (W > delta) + { + W -= delta; + } + else + { + W = 0; + } +} + +void RgbwColor::Lighten(uint8_t delta) +{ + if (IsColorLess()) + { + if (W < 255 - delta) + { + W += delta; + } + else + { + W = 255; + } + } + else + { + if (R < 255 - delta) + { + R += delta; + } + else + { + R = 255; + } + + if (G < 255 - delta) + { + G += delta; + } + else + { + G = 255; + } + + if (B < 255 - delta) + { + B += delta; + } + else + { + B = 255; + } + } +} + +RgbwColor RgbwColor::LinearBlend(RgbwColor left, RgbwColor right, float progress) +{ + return RgbwColor( left.R + ((right.R - left.R) * progress), + left.G + ((right.G - left.G) * progress), + left.B + ((right.B - left.B) * progress), + left.W + ((right.W - left.W) * progress) ); +} \ No newline at end of file diff --git a/src/RgbwColor.h b/src/RgbwColor.h new file mode 100644 index 00000000..795109bf --- /dev/null +++ b/src/RgbwColor.h @@ -0,0 +1,145 @@ +/*------------------------------------------------------------------------- +RgbwColor provides a color object that can be directly consumed by NeoPixelBus + +Written by Michael C. Miller. + +I invest time and resources providing this open source code, +please support me by dontating (see https://github.com/Makuna/NeoPixelBus) + +------------------------------------------------------------------------- +This file is part of the Makuna/NeoPixelBus library. + +NeoPixelBus is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 3 of +the License, or (at your option) any later version. + +NeoPixelBus is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with NeoPixel. If not, see +. +-------------------------------------------------------------------------*/ +#pragma once + +#include + +struct RgbColor; +struct HslColor; +struct HsbColor; + +// ------------------------------------------------------------------------ +// RgbwColor represents a color object that is represented by Red, Green, Blue +// component values and an extra White component. It contains helpful color +// routines to manipulate the color. +// ------------------------------------------------------------------------ +struct RgbwColor +{ + // ------------------------------------------------------------------------ + // Construct a RgbwColor using R, G, B, W values (0-255) + // ------------------------------------------------------------------------ + RgbwColor(uint8_t r, uint8_t g, uint8_t b, uint8_t w = 0) : + R(r), G(g), B(b), W(w) + { + }; + + // ------------------------------------------------------------------------ + // Construct a RgbColor using a single brightness value (0-255) + // This works well for creating gray tone colors + // (0) = black, (255) = white, (128) = gray + // ------------------------------------------------------------------------ + RgbwColor(uint8_t brightness) : + R(0), G(0), B(0), W(brightness) + { + }; + + // ------------------------------------------------------------------------ + // Construct a RgbwColor using RgbColor + // ------------------------------------------------------------------------ + RgbwColor(RgbColor color) : + R(color.R), + G(color.G), + B(color.B), + W(0) + { + + } + + // ------------------------------------------------------------------------ + // Construct a RgbwColor using HslColor + // ------------------------------------------------------------------------ + RgbwColor(HslColor color); + + // ------------------------------------------------------------------------ + // Construct a RgbwColor using HsbColor + // ------------------------------------------------------------------------ + RgbwColor(HsbColor color); + + // ------------------------------------------------------------------------ + // Construct a RgbwColor that will have its values set in latter operations + // CAUTION: The R,G,B, W members are not initialized and may not be consistent + // ------------------------------------------------------------------------ + RgbwColor() + { + }; + + // ------------------------------------------------------------------------ + // Returns if the color is grey, all values are equal other than white + // ------------------------------------------------------------------------ + bool IsMonotone() const + { + return (R == B && R == G); + }; + + // ------------------------------------------------------------------------ + // Returns if the color components are all zero, the white component maybe + // anything + // ------------------------------------------------------------------------ + bool IsColorLess() const + { + return (R == 0 && B == 0 && G == 0); + }; + + // ------------------------------------------------------------------------ + // CalculateBrightness will calculate the overall brightness + // NOTE: This is a simple linear brightness + // ------------------------------------------------------------------------ + uint8_t CalculateBrightness() const; + + // ------------------------------------------------------------------------ + // Darken will adjust the color by the given delta toward black + // NOTE: This is a simple linear change + // delta - (0-255) the amount to dim the color + // ------------------------------------------------------------------------ + void Darken(uint8_t delta); + + // ------------------------------------------------------------------------ + // Lighten will adjust the color by the given delta toward white + // NOTE: This is a simple linear change + // delta - (0-255) the amount to lighten the color + // ------------------------------------------------------------------------ + void Lighten(uint8_t delta); + + // ------------------------------------------------------------------------ + // LinearBlend between two colors by the amount defined by progress variable + // left - the color to start the blend at + // right - the color to end the blend at + // progress - (0.0 - 1.0) value where 0 will return left and 1.0 will return right + // and a value between will blend the color weighted linearly between them + // ------------------------------------------------------------------------ + static RgbwColor LinearBlend(RgbwColor left, RgbwColor right, float progress); + + // ------------------------------------------------------------------------ + // Red, Green, Blue, White color members (0-255) where + // (0,0,0,0) is black and (255,255,255, 0) and (0,0,0,255) is white + // Note (255,255,255,255) is extreme bright white + // ------------------------------------------------------------------------ + uint8_t R; + uint8_t G; + uint8_t B; + uint8_t W; +}; +