diff --git a/LPD8806.cpp b/LPD8806.cpp index b6632d9..035af5b 100644 --- a/LPD8806.cpp +++ b/LPD8806.cpp @@ -64,10 +64,47 @@ a 'latch' anyway. Tested. Confirmed. Fact. */ - -#include "SPI.h" #include "LPD8806.h" +#ifdef __AVR_ATtiny85__ + +// Teensy/Gemma-specific stuff for hardware-assisted SPI @ 2 MHz + +#if(F_CPU > 8000000L) + #define SPI_DELAY asm volatile("rjmp .+0"); // Burn 2 cycles +#elif(F_CPU > 4000000L) + #define SPI_DELAY asm volatile("nop"); // Burn 1 cycle +#else + #define SPI_DELAY // Run max speed +#endif + +#define SPIBIT \ + USICR = ((1< +#define spi_out(n) (void)SPI.transfer(n) + +#endif + /*****************************************************************************/ // Constructor for use with hardware SPI (specific clock/data pins): @@ -107,106 +144,104 @@ void LPD8806::begin(void) { // Change pin assignments post-constructor, switching to hardware SPI: void LPD8806::updatePins(void) { - hardwareSPI = true; + pinMode(datapin, INPUT); // Restore data and clock pins to inputs + pinMode(clkpin , INPUT); datapin = clkpin = 0; + hardwareSPI = true; // If begin() was previously invoked, init the SPI hardware now: if(begun == true) startSPI(); // Otherwise, SPI is NOT initted until begin() is explicitly called. - - // Note: any prior clock/data pin directions are left as-is and are - // NOT restored as inputs! } // Change pin assignments post-constructor, using arbitrary pins: void LPD8806::updatePins(uint8_t dpin, uint8_t cpin) { - + if(begun == true) { // If begin() was previously invoked... + // If previously using hardware SPI, turn that off: + if(hardwareSPI) { +#ifdef __AVR_ATtiny85__ + DDRB &= ~(_BV(PORTB1) | _BV(PORTB2)); +#else + SPI.end(); +#endif + } else { + pinMode(datapin, INPUT); // Restore prior data and clock pins to inputs + pinMode(clkpin , INPUT); + } + } datapin = dpin; clkpin = cpin; - clkport = dataport = 0; - clkpinmask = datapinmask = 0; - -#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) +#ifdef __AVR__ clkport = portOutputRegister(digitalPinToPort(cpin)); clkpinmask = digitalPinToBitMask(cpin); dataport = portOutputRegister(digitalPinToPort(dpin)); datapinmask = digitalPinToBitMask(dpin); #endif - if(begun == true) { // If begin() was previously invoked... - // If previously using hardware SPI, turn that off: - if(hardwareSPI == true) SPI.end(); - startBitbang(); // Regardless, now enable 'soft' SPI outputs - } // Otherwise, pins are not set to outputs until begin() is called. - - // Note: any prior clock/data pin directions are left as-is and are - // NOT restored as inputs! + // If previously begun, enable 'soft' SPI outputs now + if(begun == true) startBitbang(); hardwareSPI = false; } -#ifndef SPI_CLOCK_DIV8 - #define SPI_CLOCK_DIV8 4 -#endif - // Enable SPI hardware and set up protocol details: void LPD8806::startSPI(void) { +#ifdef __AVR_ATtiny85__ + PORTB &= ~(_BV(PORTB1) | _BV(PORTB2)); // Outputs + DDRB |= _BV(PORTB1) | _BV(PORTB2); // DO (NOT MOSI) + SCK +#else SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - - SPI.setClockDivider(SPI_CLOCK_DIV8); // 2 MHz // SPI bus is run at 2MHz. Although the LPD8806 should, in theory, // work up to 20MHz, the unshielded wiring from the Arduino is more // susceptible to interference. Experiment and see what you get. - -#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) + #if defined(__AVR__) || defined(CORE_TEENSY) + SPI.setClockDivider(SPI_CLOCK_DIV8); + #else + SPI.setClockDivider((F_CPU + 1000000L) / 2000000L); + #endif +#endif // Issue initial latch/reset to strip: - SPDR = 0; // Issue initial byte - for(uint16_t i=((numLEDs+31)/32)-1; i>0; i--) { - while(!(SPSR & (1<0; i--) { - SPI.transfer(0); - } -#endif + for(uint16_t i=((numLEDs+31)/32); i>0; i--) spi_out(0); } // Enable software SPI pins and issue initial latch: void LPD8806::startBitbang() { pinMode(datapin, OUTPUT); pinMode(clkpin , OUTPUT); - if (dataport != 0) { - // use low level bitbanging when we can - *dataport &= ~datapinmask; // Data is held low throughout (latch = 0) - for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) { - *clkport |= clkpinmask; - *clkport &= ~clkpinmask; - } - } else { - // can't do low level bitbanging, revert to digitalWrite - digitalWrite(datapin, LOW); - for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) { - digitalWrite(clkpin, HIGH); - digitalWrite(clkpin, LOW); - } +#ifdef __AVR__ + *dataport &= ~datapinmask; // Data is held low throughout (latch = 0) + for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) { + *clkport |= clkpinmask; + *clkport &= ~clkpinmask; + } +#else + digitalWrite(datapin, LOW); + for(uint16_t i=((numLEDs+31)/32)*8; i>0; i--) { + digitalWrite(clkpin, HIGH); + digitalWrite(clkpin, LOW); } +#endif } // Change strip length (see notes with empty constructor, above): void LPD8806::updateLength(uint16_t n) { - uint8_t latchBytes = (n + 31) / 32; - if(pixels != NULL) free(pixels); // Free existing data (if any) - numLEDs = n; - n *= 3; // 3 bytes per pixel - numBytes = n + latchBytes; - if(NULL != (pixels = (uint8_t *)malloc(numBytes))) { // Alloc new data - memset( pixels , 0x80, n); // Init to RGB 'off' state - memset(&pixels[n], 0 , latchBytes); // Clear latch bytes - } else numLEDs = numBytes = 0; // else malloc failed + uint8_t latchBytes; + uint16_t dataBytes, totalBytes; + + numLEDs = numBytes = 0; + if(pixels) free(pixels); // Free existing data (if any) + + dataBytes = n * 3; + latchBytes = (n + 31) / 32; + totalBytes = dataBytes + latchBytes; + if((pixels = (uint8_t *)malloc(totalBytes))) { // Alloc new data + numLEDs = n; + numBytes = totalBytes; + memset( pixels , 0x80, dataBytes); // Init to RGB 'off' state + memset(&pixels[dataBytes], 0 , latchBytes); // Clear latch bytes + } // 'begun' state does not change -- pins retain prior modes } @@ -214,10 +249,6 @@ uint16_t LPD8806::numPixels(void) { return numLEDs; } -// This is how data is pushed to the strip. Unfortunately, the company -// that makes the chip didnt release the protocol document or you need -// to sign an NDA or something stupid like that, but we reverse engineered -// this from a strip controller and it seems to work very nicely! void LPD8806::show(void) { uint8_t *ptr = pixels; uint16_t i = numBytes; @@ -226,31 +257,24 @@ void LPD8806::show(void) { // bytes vs. latch data, etc. Everything is laid out in one big // flat buffer and issued the same regardless of purpose. if(hardwareSPI) { - while(i--) { -#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328P__) || defined (__AVR_ATmega328__) || defined(__AVR_ATmega8__) || (__AVR_ATmega1281__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1280__) - while(!(SPSR & (1<>= 1) { - if (dataport != 0) { +#ifdef __AVR__ if(p & bit) *dataport |= datapinmask; else *dataport &= ~datapinmask; *clkport |= clkpinmask; *clkport &= ~clkpinmask; - } else { - if (p&bit) digitalWrite(datapin, HIGH); - else digitalWrite(datapin, LOW); +#else + if(p & bit) digitalWrite(datapin, HIGH); + else digitalWrite(datapin, LOW); digitalWrite(clkpin, HIGH); digitalWrite(clkpin, LOW); - } +#endif } } } diff --git a/LPD8806.h b/LPD8806.h index 126ef1c..3468c37 100644 --- a/LPD8806.h +++ b/LPD8806.h @@ -14,9 +14,9 @@ class LPD8806 { LPD8806(void); // Empty constructor; init pins & strip length later void begin(void), - show(void), setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b), setPixelColor(uint16_t n, uint32_t c), + show(void), updatePins(uint8_t dpin, uint8_t cpin), // Change pins, configurable updatePins(void), // Change pins, hardware SPI updateLength(uint16_t n); // Change strip length @@ -32,11 +32,14 @@ class LPD8806 { numLEDs, // Number of RGB LEDs in strip numBytes; // Size of 'pixels' buffer below uint8_t - *pixels, // Holds LED color values (3 bytes each) + latch - clkpin , datapin, // Clock & data pin numbers + *pixels, // Holds LED color values (3 bytes each) + latch bytes + clkpin , datapin; // Clock & data pin numbers +#ifdef __AVR__ + uint8_t clkpinmask, datapinmask; // Clock & data PORT bitmasks volatile uint8_t *clkport , *dataport; // Clock & data PORT registers +#endif void startBitbang(void), startSPI(void); diff --git a/examples/LEDbeltKit/LEDbeltKit.pde b/examples/LEDbeltKit/LEDbeltKit.pde index 3ca0c7b..ccfabbb 100644 --- a/examples/LEDbeltKit/LEDbeltKit.pde +++ b/examples/LEDbeltKit/LEDbeltKit.pde @@ -2,6 +2,7 @@ #include "SPI.h" // Example to control LPD8806-based RGB LED Modules in a strip! +// NOTE: WILL NOT WORK ON TRINKET OR GEMMA due to floating-point math /*****************************************************************************/ #if defined(USB_SERIAL) || defined(USB_SERIAL_ADAFRUIT) @@ -19,8 +20,6 @@ int clockPin = 15; // The LED strips are 32 LEDs per meter but you can extend/cut the strip LPD8806 strip = LPD8806(32, dataPin, clockPin); - - void setup() { // Start up the LED strip strip.begin(); @@ -252,4 +251,4 @@ uint32_t Wheel(uint16_t WheelPos) break; } return(strip.Color(r,g,b)); -} \ No newline at end of file +} diff --git a/examples/LEDbeltKit_alt/LEDbeltKit_alt.pde b/examples/LEDbeltKit_alt/LEDbeltKit_alt.pde index 3cb94e5..f00c4f0 100644 --- a/examples/LEDbeltKit_alt/LEDbeltKit_alt.pde +++ b/examples/LEDbeltKit_alt/LEDbeltKit_alt.pde @@ -3,6 +3,8 @@ // ANIMATION. See the 'strandtest' sketch for a simpler and less RAM- // intensive example that can process more LEDs (100+). +// ALSO: NOT COMPATIBLE WITH TRINKET OR GEMMA for way too many reasons. + // Example to control LPD8806-based RGB LED Modules in a strip; originally // intended for the Adafruit Digital Programmable LED Belt Kit. // REQUIRES TIMER1 LIBRARY: http://www.arduino.cc/playground/Code/Timer1 diff --git a/examples/longstrandtest/longstrandtest.pde b/examples/longstrandtest/longstrandtest.pde index 42af884..4d33577 100644 --- a/examples/longstrandtest/longstrandtest.pde +++ b/examples/longstrandtest/longstrandtest.pde @@ -2,6 +2,7 @@ #include "SPI.h" // Simple test for 160 (5 meters) of LPD8806-based RGB LED strip +// Not compatible with Trinket/Gemma due to limited RAM /*****************************************************************************/ diff --git a/examples/strandtest/strandtest.ino b/examples/strandtest/strandtest.ino index f37a7a9..0b152ed 100644 --- a/examples/strandtest/strandtest.ino +++ b/examples/strandtest/strandtest.ino @@ -1,5 +1,8 @@ #include "LPD8806.h" -#include "SPI.h" +#include "SPI.h" // Comment out this line if using Trinket or Gemma +#ifdef __AVR_ATtiny85__ + #include +#endif // Example to control LPD8806-based RGB LED Modules in a strip @@ -26,6 +29,10 @@ LPD8806 strip = LPD8806(nLEDs, dataPin, clockPin); //LPD8806 strip = LPD8806(nLEDs); void setup() { +#if defined(__AVR_ATtiny85__) && (F_CPU == 16000000L) + clock_prescale_set(clock_div_1); // Enable 16 MHz on Trinket +#endif + // Start up the LED strip strip.begin();