/*----------------------------------------------------------------------------------------------- Arduino library to control SK6812 RGB Led strips using the Arduino STM32 LibMaple core ----------------------------------------------------------------------------------------------- Note. This library has only been tested on the SK6812 LED. It may not work with the older WS2812 or other types of addressable RGB LED, becuase it relies on a division multiple of the 72Mhz clock frequence on the STM32 SPI to generate the correct width T0H pulse, of 400ns +/- 150nS SPI DIV32 gives a pulse width of 444nS which is well within spec for the SK6812 but is probably too long for the WS2812 which needs a 350ns pulse for T0H This WS2811B 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 3 of the License, or (at your option) any later version. It 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. See . -----------------------------------------------------------------------------------------------*/ #include "SK6812.h" #include "pins_arduino.h" #include "wiring_private.h" #include // Constructor when n is the number of LEDs in the strip SK6812::SK6812(uint16_t number_of_leds) : brightness(0), pixels(NULL), doubleBuffer(NULL), begun(false) { updateLength(number_of_leds); } SK6812::~SK6812() { if(pixels) { free(pixels); } SPI.end(); } void SK6812::begin(void) { if (!begun) { SPI.setClockDivider(SPI_CLOCK_DIV4);// need bit rate of 400nS but closest we can do @ 72Mhz is 444ns (which is within spec) SPI.setBitOrder(LSBFIRST); SPI.begin(); begun = true; } } void SK6812::updateLength(uint16_t n) { if(doubleBuffer) { free(doubleBuffer); } numBytes = (n<<5) + n + 2; // 9 encoded bytes per pixel. 1 byte empty peamble to fix issue with SPI MOSI and on byte at the end to clear down MOSI // Note. (n<<3) +n is a fast way of doing n*9 if((doubleBuffer = (uint8_t *)malloc(numBytes*2))) { numLEDs = n; pixels = doubleBuffer; // Only need to init the part of the double buffer which will be interacted with by the API e.g. setPixelColor *pixels=0;//clear the preamble byte *(pixels+numBytes-1)=0;// clear the post send cleardown byte. clear();// Set the encoded data to all encoded zeros } else { numLEDs = numBytes = 0; } } // Sends the current buffer to the leds void SK6812::show(void) { SPI.dmaSendAsync(pixels,numBytes);// Start the DMA transfer of the current pixel buffer to the LEDs and return immediately. // Need to copy the last / current buffer to the other half of the double buffer as most API code does not rebuild the entire contents // from scratch. Often just a few pixels are changed e.g in a chaser effect if (pixels==doubleBuffer) { // pixels was using the first buffer pixels = doubleBuffer+numBytes; // set pixels to second buffer memcpy(pixels,doubleBuffer,numBytes);// copy first buffer to second buffer } else { // pixels was using the second buffer pixels = doubleBuffer; // set pixels to first buffer memcpy(pixels,doubleBuffer+numBytes,numBytes); // copy second buffer to first buffer } } /*Sets a specific pixel to a specific r,g,b colour * Because the pixels buffer contains the encoded bitstream, which is in triplets * the lookup table need to be used to find the correct pattern for each byte in the 3 byte sequence. */ void SK6812::setPixelColor(uint16_t n, uint8_t r, uint8_t g, uint8_t b) { uint8_t *bptr = pixels + (n<<5) + n +1; //uint8_t *tPtr = (uint8_t *)encoderLookup + g*2 + g;// need to index 3 x g into the lookup uint8_t *tPtr = (uint8_t *)encoderLookup + g*11;// need to index 3 x g into the lookup *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; tPtr = (uint8_t *)encoderLookup + r*11; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; tPtr = (uint8_t *)encoderLookup + b*11; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; } void SK6812::setPixelColor(uint16_t n, uint32_t c) { uint8_t r,g,b; if(brightness) { r = ((int)((uint8_t)(c >> 16)) * (int)brightness) >> 8; g = ((int)((uint8_t)(c >> 8)) * (int)brightness) >> 8; b = ((int)((uint8_t)c) * (int)brightness) >> 8; } else { r = (uint8_t)(c >> 16), g = (uint8_t)(c >> 8), b = (uint8_t)c; } uint8_t *bptr = pixels + (n<<5) + n +1; uint8_t *tPtr = (uint8_t *)encoderLookup + g*11;// need to index 3 x g into the lookup *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; tPtr = (uint8_t *)encoderLookup + r*11; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; tPtr = (uint8_t *)encoderLookup + b*11; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; } // Convert separate R,G,B into packed 32-bit RGB color. // Packed format is always RGB, regardless of LED strand color order. uint32_t SK6812::Color(uint8_t r, uint8_t g, uint8_t b) { return ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } // Convert separate R,G,B,W into packed 32-bit WRGB color. // Packed format is always WRGB, regardless of LED strand color order. uint32_t SK6812::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t w) { return ((uint32_t)w << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; } uint16_t SK6812::numPixels(void) const { return numLEDs; } // Adjust output brightness; 0=darkest (off), 255=brightest. This does // NOT immediately affect what's currently displayed on the LEDs. The // next call to show() will refresh the LEDs at this level. However, // this process is potentially "lossy," especially when increasing // brightness. The tight timing in the WS2811/WS2812 code means there // aren't enough free cycles to perform this scaling on the fly as data // is issued. So we make a pass through the existing color data in RAM // and scale it (subsequent graphics commands also work at this // brightness level). If there's a significant step up in brightness, // the limited number of steps (quantization) in the old data will be // quite visible in the re-scaled version. For a non-destructive // change, you'll need to re-render the full strip data. C'est la vie. void SK6812::setBrightness(uint8_t b) { // Stored brightness value is different than what's passed. // This simplifies the actual scaling math later, allowing a fast // 8x8-bit multiply and taking the MSB. 'brightness' is a uint8_t, // adding 1 here may (intentionally) roll over...so 0 = max brightness // (color values are interpreted literally; no scaling), 1 = min // brightness (off), 255 = just below max brightness. uint8_t newBrightness = b + 1; if(newBrightness != brightness) { // Compare against prior value // Brightness has changed -- re-scale existing data in RAM uint8_t c, *ptr = pixels, oldBrightness = brightness - 1; // De-wrap old brightness value uint16_t scale; if(oldBrightness == 0) scale = 0; // Avoid /0 else if(b == 255) scale = 65535 / oldBrightness; else scale = (((uint16_t)newBrightness << 8) - 1) / oldBrightness; for(uint16_t i=0; i> 8; } brightness = newBrightness; } } //Return the brightness value uint8_t SK6812::getBrightness(void) const { return brightness - 1; } /* * Sets the encoded pixel data to turn all the LEDs off. */ void SK6812::clear() { uint8_t * bptr= pixels+1;// Note first byte in the buffer is a preable and is always zero. hence the +1 uint8_t *tPtr; for(int i=0;i< (numLEDs *3);i++) { tPtr = (uint8_t *)encoderLookup; *bptr++ = *tPtr++; *bptr++ = *tPtr++; *bptr++ = *tPtr++; } }