How not to Build a POV Display Using WS2812B Neopixel LEDs and ESP8266

Published  April 21, 2022   0
DIY POV Display

For one of our projects at circuit digest, we wanted to build a Persistence of Vision Display or POV display with WS2812B LEDs. While making the project, our only focus was on how we could make it simpler along with less hardware. As a result, we chose some simple WS2812B RGB LEDs and an ESP8266-01 as the brains for this project because they are lightweight and cost less, but little did we know that this hardware design would invite plenty of troubles.

So, in this article, we'll show you how to avoid making a POV display with WS2812B RGB LEDs due to timing concerns, as well as how to use this simple hardware design as a random pattern generator with its own web server. Based on this experience we also built a mono-color POV display using ESP32 which works very well, you can also check that out if intrested. So without further ado let's get right into it.

POV Displays

In one of our previous projects, we have built a simple Arduino Propeller LED Display that uses POV technology to display text. You can check that out if you want to print text on a self-made POV display. Other than that we have built many projects with WS2812B RGB LEDS. Some interesting ones are linked down below.

Components Required to Build Arduino Based POV Display

The components required to build the POV Display are easy to acquire and cheap. The component list is given below.

  • ESP8266 - 01
  • WS2812B RGB LEDS
  • 3.7V 400mA Lithium Battery
  • 12V DC Motor
  • Perfboard
  • Wires
  • Connectors
  • Two Component Adhesive
  • And a 12V battery (to supply the motor)

ESP8266 POV Display Circuit Diagram

The schematic diagram for the WS2812B and ESP8266-01 based POV Display is shown below and as you can see it's pretty simple and easy to understand.

POV Display using ESP8266 Circuit Diagram

The brain of the circuit is the ESP8266-01 module which is powered by a 3.7V, 400mA Lithium battery, and the WS2812B LEDs are connected to the GPIO0 of the ESP8266-01 module. There are a total of 8 LEDs in the Neopixels, and we utilised a self-module to power them. If you don't have the module you can use LED strips as a replacement. In this circuit, we have used the GPIO0 instead of GPIO2 because after testing a lot of ESP8266-01 modules we have found that the GPIO0 is more stable than the GPIO2 of the module. Please note that while making this project we did not put any circuitry for charging the battery, because it will make the board heavy and it will be very difficult to drive that thing.

POV Display Setup

So for any POV Display, we need a powerful motor to work with and for that, we have used a 200RPM gear motor. We utilized it since we had multiple gear motors laying around at the time of the project, so we disassembled one to get the motor out, as shown below.-

200RPM Gear Motor

To mount the motor, we have used a big aluminum channel with a small 3D printed L-shaped holder that is holding the motor to the aluminum channel. An image of the 3D printed channel is shown below-

DIY POV Display

To power the motor, we're using a 5V power supply from our lab bench.

Arduino Code to Build WS2812 Neopixels based POV Display

Now that we know all the hardware and working of this project, we can move on to the code portion of this project. The code portion for this project is not small but it's easy to understand.

We start off our code by including all the required libraries and defining all the necessary pins and parameters that we need to run WS2812B LEDs WI-Fi and OTA.

// Load Wi-Fi library, Fast-LED Library and Custom OTA Library
#include <ESP8266WiFi.h>
#include <FastLED.h>
#include "OTA.h"
#define LED_PIN     0 // LED is connected to the IO-0 of the ESP8266-01
#define NUM_LEDS    8 // We have 8 LEDs in the strip
#define BRIGHTNESS  50 // set the brightness for the LEDs
#define LED_TYPE    WS2812 // We are using WS2812B LEDs
#define COLOR_ORDER GRB // Colour order of the LEDs
#define UPDATES_PER_SECOND 100 // FPS

Next, we create objects for the RGB LEDs and we also create the server object on port 80.

CRGB leds[NUM_LEDS]; // Create LED array for manipulation
CRGBPalette16 currentPalette; // set color palet type
TBlendType    currentBlending;// set blend mode
extern CRGBPalette16 myRedWhiteBluePalette; // create object for different palet
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
// Set web server port number to 80
WiFiServer server(80);

Next, we declare all the required variables, including variables that stores the button states named Animation_1, Animation_2, and Animation_3, and variable to store the total html webpage which is named header. Finally, there are two sets of variable that is used to store the SSID and password for the Wi-Fi hotspot.

// variables to store the current Button state
String Animation_1 = "off";
String Animation_2 = "off";
String Animation_3 = "off";
// Variable to store the HTTP request
String header;
const char* ssid = "Your SSID";
const char* password = "Your Password";
unsigned long currentTime = millis();
// Previous time
unsigned long previousTime = 0;
// Define timeout time in milliseconds (example: 2000ms = 2s)
const long timeoutTime = 2000;

Next, we have our setup() function. In the setup function, we have our usual stuff like enabling the serial for debugging, setup OTA, setup the Neopixels, and setting up the webserver.

void setup() {
  Serial.begin(115200);
  Serial.println("Booting");
  setupOTA("POV_Display", ssid , password);
  delay( 300 ); // power-up safety delay
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness(  BRIGHTNESS );
  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;
  server.begin();
}

Next, we have a list of patterns which we have defined with a pointer that will be called on when we switch between different patterns.

// List of patterns to cycle through.  Each is defined as a separate function below.
typedef void (*SimplePatternList[])();
SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm };
uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current
uint8_t gHue = 0; // rotating "base color" used by many of the patterns

Next, we have our loop section. In the loop section, we call the ArduinoOTA.handle() function so that we could do an OTA update of the ESP while the ESP is mounted on the PCB and attached to the motor. Next, we declare and define the WiFiClient object client, and check if any new connections are available or not.

void loop() {
  while ((unsigned long) millis() < 10000)
  {
    ArduinoOTA.handle();
    delay(10);
  }
  ArduinoOTA.handle();
  WiFiClient client = server.available();   // Listen for incoming clients

Now, if a new client connects and the statement becomes true, we take the current timestamp and store it in the currentTime variable and next we store the same data to the previous time variable for later use. Next, we print a statement to the serial monitor window to notify the user that a new user got connected and requested the webpage. We also define a string type variable to hold that incoming data so that we can parse the data later and check if the request is exactly what we want.

 if (client) { // If a new client connects,
    currentTime = millis();
    previousTime = currentTime;
    Serial.println("New Client"); // print a statement so the user knows that a new client got connected
    String incomingData = "";  // make a String to hold incoming data from client

Next, we define a while loop, this while loop is only here to serve the timeout sequence. If the timeout delay exceeds, the while loop will simply break. Inside the while loop, we simply check if any data is available on the client-side, if so then we create a local variable named read_byte and store the entire bitstream that is coming from the client. Then we print out the entire bitstream on the serial monitor window for debugging. Finally, we store the total bitstream inside the string type variable called header.  

    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // client connects and request the page from server
      currentTime = millis();
      if (client.available()) {             // if clent sends a byte to read
        char read_byte = client.read();             // read that byte from client
        Serial.write(read_byte);                    // print it to the serial monitor for debugging
        header += read_byte;

Next, we check if our byte variable contains a “\n” character or not. If so, then we can say that it's the end of the HTTP request. So now we can send our webpage as a response. In the next section of the code, we simply send the entire web page as a response. The response always starts with an OK response so at first, we send that when we know the request is complete. After that, we send other necessary parameters.

if (incomingData.length() == 0) {
            // HTTP response code always start with a oK statement  (e.g. HTTP/1.1 200 OK)
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();

The next part of the code is the most important part because in this section we have received the data from our web server and updated the string type variables that we have declared earlier and in the loop function, we are checking this variable and updating the LEDs.       

            //Set Different Mode For the LEDs
            if (header.indexOf("GET /Animation_1/on") >= 0) {
              // Serial.println("Animation 1 on");
              Animation = "on";
            } else if (header.indexOf("GET /Animation_1/off") >= 0) {
              // Serial.println("Animation 1 off");
              Animation = "off";
            } else if (header.indexOf("GET /Animation_2/on") >= 0) {
              // Serial.println("Animation 2 on");
              Animation_2 = "on";
            } else if (header.indexOf("GET /Animation_2/off") >= 0) {
              // Serial.println("Animation 2 off");
              Animation = "off";
            } else if (header.indexOf("GET /Animation_3/on") >= 0) {
              // Serial.println("Animation 3 on");
              Animation = "on";
            } else if (header.indexOf("GET /Animation_3/off") >= 0) {
              // Serial.println("Animation 3 off");
              Animation = "off";
            }

In the next part, we send the whole webpage to the client as a response and in this section, we mainly send the HTML and CSS files using the client.println() function.

 client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");
            client.println(".button { background-color: #3bc041; color: #ffffff; padding: 9px 30px ; text-decoration: none; font-size: 30px; margin: 2px ; border-radius: 5px ; border: 1px solid #ddffdf; box-shadow: 1px 2px 3px #0000001c; ");
            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");
            client.println(".button:hover{ background-color: #31b237; }");
            client.println(".button2 { background-color: #aaaaaa; border: 1px solid #bbbbbb; }</style></head>");
            // This is the heading of the webpage
            client.println("<body><h1>ESP32 Web Server</h1>");

In the next part of the code, we update the webpage with the current state of the button and check for any changes in the button state. If a state change has occurred, we update the changes with the client.println() method. We do this for all three buttons. Finally, we end the response by sending a blank statement.

         // Display current state, of the button 1
            client.println("<p>Animation 1 - State " + Animation_1 + "</p>");
            if (Animation_1 == "off")
              client.println("<p><a href=\"/Animation_1/on\"><button class=\"button\">ON</button></a></p>");
            else
              client.println("<p><a href=\"/Animation_1/off\"><button class=\"button button2\">OFF</button></a></p>");
            // Display current state, of the button 2
            client.println("<p>Animation 2 - State " + Animation_2 + "</p>");
            if (Animation_2 == "off")
              client.println("<p><a href=\"/Animation_2/on\"><button class=\"button\">ON</button></a></p>");
            else
              client.println("<p><a href=\"/Animation_2/off\"><button class=\"button button2\">OFF</button></a></p>");
            // Display current state, of the button 3
            client.println("<p>Animation 3 - State " + Animation_3 + "</p>");
            if (Animation_3 == "off")
              client.println("<p><a href=\"/Animation_3/on\"><button class=\"button\">ON</button></a></p>");
            else
              client.println("<p><a href=\"/Animation_3/off\"><button class=\"button button2\">OFF</button></a></p>");
            client.println("</body></html>");
            // The HTTP response ends with another blank line
            client.println();

Next, we have some else and else if statements that handle any exceptions that occur during the server-client communication process. Now if everything is correct and executed successfully, we clear our main header variable and we terminate the connection.

          else  // if you got a newline, then clear incomingData
            incomingData = "";
        }
        else if (read_byte != '\r')   // if you got anything else but a carriage return character,
         incomingData += read_byte;      // add it to the end of the currentLine
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected");
    Serial.println("\n");
  }

If the above process finishes successfully, we are back within our main loop function and in the next, in the loop function, we check for any updates in our string type variables Animation_1, Animation_2, and Animation_3. If any changes have occurred, we call the corresponding function and light up the display. If every button is on, we turn off the POV display.

 if (Animation_1 == "on")
  {
    color_palette();
  }
  else if (Animation_2 == "on")
  {
    animation();
    //cyclone_colour();
  }
  else if (Animation_3 == "on")
  {
    //first_light();
    demo_reel100();
  }
  else if (Animation_1 == "off" && Animation_2 == "off" && Animation_3 == "off"
  {
    for (int i = 0; i < 8; i++)
    {
      leds[i] = CRGB::Black;
      FastLED.show();
      delay(2);
    }
  }

This marks the end of the code portion, but as you can see in the code, there is a huge portion of the code that is not explained because this entire portion of the code is taken from the fast LED library and you can check out the documentation for fastled library for more information. As we have displayed only three buttons, you can only choose between three patterns but there are other patterns available in the code as a function that you can call and display.

void color_palette()
{
  Serial.println("working");
  ChangePalettePeriodically();
  static uint8_t startIndex = 0;
  startIndex = startIndex + 1; /* motion speed */
  FillLEDsFromPaletteColors( startIndex);
  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
}
void cyclone_colour()
{
  static uint8_t hue = 0;
  Serial.print("x");
  // First slide the led in one direction
  for (int i = 0; i < NUM_LEDS; i++) {
    // Set the i'th led to red
    leds[i] = CHSV(hue++, 255, 255);
    // Show the leds
    FastLED.show();
    // now that we've shown the leds, reset the i'th led to black
    // leds[i] = CRGB::Black;
    fadeall();
    // Wait a little bit before we loop around and do it again
    delay(10);
  }
  Serial.print("x");
  // Now go in the other direction.
  for (int i = (NUM_LEDS) - 1; i >= 0; i--) {
    // Set the i'th led to red
    leds[i] = CHSV(hue++, 255, 255);
    // Show the leds
    FastLED.show();
    // now that we've shown the leds, reset the i'th led to black
    // leds[i] = CRGB::Black;
    fadeall();
    // Wait a little bit before we loop around and do it again
    delay(10);
  }
}
void animation()
{
  EVERY_N_MILLISECONDS( 20) {
    pacifica_loop();
    FastLED.show();
  }
}
void fadeall() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].nscale8(250);
  }
}
void first_light()
{
  // Move a single white led
  for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed = whiteLed + 1) {
    // Turn our current led on to white, then show the leds
    leds[whiteLed] = CRGB::White;
    // Show the leds (only one of which is set to white, from above)
    FastLED.show();
    // Wait a little bit
    delay(100);
    // Turn our current led back to black for the next loop around
    leds[whiteLed] = CRGB::Black;
  }
}
void demo_reel100()
{
  gPatterns[gCurrentPatternNumber]();
  // send the 'leds' array out to the actual LED strip
  FastLED.show();
  // insert a delay to keep the framerate modest
  FastLED.delay(1000 / UPDATES_PER_SECOND);
  // do some periodic updates
  EVERY_N_MILLISECONDS( 20 ) {
    gHue++;  // slowly cycle the "base color" through the rainbow
  }
  EVERY_N_SECONDS( 10 ) {
    nextPattern();  // change patterns periodically
  }
}
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
  uint8_t brightness = 255;
  for ( int i = 0; i < NUM_LEDS; ++i) {
    leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
    colorIndex += 3;
  }
}
void rgb_wheel()
{
  for (int i = 0; i < 8; i++)
    leds[i] = CRGB::Red;
  FastLED.show();
  delay(5);
  for (int i = 0; i < 8; i++)
    leds[i] = CRGB::Blue;
  FastLED.show();
  delay(5);
  for (int i = 0; i < 8; i++)
    leds[i] = CRGB::Green;
  FastLED.show();
  delay(5);
  for (int i = 0; i < 8; i++)
    leds[i] = CRGB::Black;
  FastLED.show();
}
void ChangePalettePeriodically()
{
  uint8_t secondHand = (millis() / 1000) % 60;
  static uint8_t lastSecond = 99;
  if ( lastSecond != secondHand) {
    lastSecond = secondHand;
    if ( secondHand ==  0)  {
      currentPalette = RainbowColors_p;
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 10)  {
      currentPalette = RainbowStripeColors_p;
      currentBlending = NOBLEND;
    }
    if ( secondHand == 15)  {
      currentPalette = RainbowStripeColors_p;
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 20)  {
      SetupPurpleAndGreenPalette();
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 25)  {
      SetupTotallyRandomPalette();
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 30)  {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = NOBLEND;
    }
    if ( secondHand == 35)  {
      SetupBlackAndWhiteStripedPalette();
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 40)  {
      currentPalette = CloudColors_p;
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 45)  {
      currentPalette = PartyColors_p;
      currentBlending = LINEARBLEND;
    }
    if ( secondHand == 50)  {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = NOBLEND;
    }
    if ( secondHand == 55)  {
      currentPalette = myRedWhiteBluePalette_p;
      currentBlending = LINEARBLEND;
    }
  }
}
// This function fills the palette with totally random colors.
void SetupTotallyRandomPalette()
{
  for ( int i = 0; i < 16; ++i) {
    currentPalette[i] = CHSV( random8(), 255, random8());
  }
}
// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
  // 'black out' all 16 palette entries...
  fill_solid( currentPalette, 16, CRGB::Black);
  // and set every fourth one to white.
  currentPalette[0] = CRGB::White;
  currentPalette[4] = CRGB::White;
  currentPalette[8] = CRGB::White;
  currentPalette[12] = CRGB::White;
}
// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
  CRGB purple = CHSV( HUE_PURPLE, 255, 255);
  CRGB green  = CHSV( HUE_GREEN, 255, 255);
  CRGB black  = CRGB::Black;
  currentPalette = CRGBPalette16(
                     green,  green,  black,  black,
                     purple, purple, black,  black,
                     green,  green,  black,  black,
                     purple, purple, black,  black );
}
// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =
{
  CRGB::Red,
  CRGB::Gray, // 'white' is too bright compared to red and blue
  CRGB::Blue,
  CRGB::Black,
  CRGB::Red,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Black,
  CRGB::Red
  CRGB::Red,
  CRGB::Gray,
  CRGB::Gray,
  CRGB::Blue,
  CRGB::Blue,
  CRGB::Black,
  CRGB::Black
};
#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))
void nextPattern()
{
  // add one to the current pattern number, and wrap around at the end
  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);
}
void rainbow()
{
  // FastLED's built-in rainbow generator
  fill_rainbow( leds, NUM_LEDS, gHue, 7);
}
void rainbowWithGlitter()
{
  // built-in FastLED rainbow, plus some random sparkly glitter
  rainbow();
  addGlitter(80);
}
void addGlitter( fract8 chanceOfGlitter)
{
  if ( random8() < chanceOfGlitter) {
    leds[ random16(NUM_LEDS) ] += CRGB::White;
  }
}
void confetti()
{
  // random colored speckles that blink in and fade smoothly
  fadeToBlackBy( leds, NUM_LEDS, 10);
  int pos = random16(NUM_LEDS);
  leds[pos] += CHSV( gHue + random8(64), 200, 255);
}
void sinelon()
{
  // a colored dot sweeping back and forth, with fading trails
  fadeToBlackBy( leds, NUM_LEDS, 20);
  int pos = beatsin16( 13, 0, NUM_LEDS - 1 );
  leds[pos] += CHSV( gHue, 255, 192);
}
void bpm()
{
  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)
  uint8_t BeatsPerMinute = 62;
  CRGBPalette16 palette = PartyColors_p;
  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);
  for ( int i = 0; i < NUM_LEDS; i++) { //9948
    leds[i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10));
  }
}
void juggle() {
  // eight colored dots, weaving in and out of sync with each other
  fadeToBlackBy( leds, NUM_LEDS, 20);
  uint8_t dothue = 0;
  for ( int i = 0; i < 8; i++) {
    leds[beatsin16( i + 7, 0, NUM_LEDS - 1 )] |= CHSV(dothue, 200, 255);
    dothue += 32;
  }
}
CRGBPalette16 pacifica_palette_1 =
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
  0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50
};
CRGBPalette16 pacifica_palette_2 =
{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,
  0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F
};
CRGBPalette16 pacifica_palette_3 =
{ 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33,
  0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF
};
void pacifica_loop()
{
  // Increment the four "color index start" counters, one for each wave layer.
  // Each is incremented at a different speed, and the speeds vary over time.
  static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4;
  static uint32_t sLastms = 0;
  uint32_t ms = GET_MILLIS();
  uint32_t deltams = ms - sLastms;
  sLastms = ms;
  uint16_t speedfactor1 = beatsin16(3, 179, 269);
  uint16_t speedfactor2 = beatsin16(4, 179, 269);
  uint32_t deltams1 = (deltams * speedfactor1) / 256;
  uint32_t deltams2 = (deltams * speedfactor2) / 256;
  uint32_t deltams21 = (deltams1 + deltams2) / 2;
  sCIStart1 += (deltams1 * beatsin88(1011, 10, 13));
  sCIStart2 -= (deltams21 * beatsin88(777, 8, 11));
  sCIStart3 -= (deltams1 * beatsin88(501, 5, 7));
  sCIStart4 -= (deltams2 * beatsin88(257, 4, 6));
  // Clear out the LED array to a dim background blue-green
  fill_solid( leds, NUM_LEDS, CRGB( 2, 6, 10));
  // Render each of four layers, with different scales and speeds, that vary over time
  pacifica_one_layer( pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0 - beat16( 301) );
  pacifica_one_layer( pacifica_palette_2, sCIStart2, beatsin16( 4,  6 * 256,  9 * 256), beatsin8( 17, 40,  80), beat16( 401) );
  pacifica_one_layer( pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10, 38), 0 - beat16(503));
  pacifica_one_layer( pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10, 28), beat16(601));
  // Add brighter 'whitecaps' where the waves lines up more
  pacifica_add_whitecaps();
  // Deepen the blues and greens a bit
  pacifica_deepen_colors();
}
// Add one layer of waves into the led array
void pacifica_one_layer( CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff)
{
  uint16_t ci = cistart;
  uint16_t waveangle = ioff;
  uint16_t wavescale_half = (wavescale / 2) + 20;
  for ( uint16_t i = 0; i < NUM_LEDS; i++) {
    waveangle += 250;
    uint16_t s16 = sin16( waveangle ) + 32768;
    uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half;
    ci += cs;
    uint16_t sindex16 = sin16( ci) + 32768;
    uint8_t sindex8 = scale16( sindex16, 240);
    CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND);
    leds[i] += c;
  }
}
// Add extra 'white' to areas where the four layers of light have lined up brightly
void pacifica_add_whitecaps()
{
  uint8_t basethreshold = beatsin8( 9, 55, 65);
  uint8_t wave = beat8( 7 );
  for ( uint16_t i = 0; i < NUM_LEDS; i++) {
    uint8_t threshold = scale8( sin8( wave), 20) + basethreshold;
    wave += 7;
    uint8_t l = leds[i].getAverageLight();
    if ( l > threshold) {
      uint8_t overage = l - threshold;
      uint8_t overage2 = qadd8( overage, overage);
      leds[i] += CRGB( overage, overage2, qadd8( overage2, overage2));
    }
  }
}
// Deepen the blues and greens
void pacifica_deepen_colors()
{
  for ( uint16_t i = 0; i < NUM_LEDS; i++) {
    leds[i].blue = scale8( leds[i].blue,  145);
    leds[i].green = scale8( leds[i].green, 200);
    leds[i] |= CRGB( 2, 5, 7);
  }
}

Once the code is uploaded and you get your IP in the serial monitor window, you get something that is shown above. By pressing the activate button, you can change the animation that is shown in the display.

POV Display Controller

Testing the Neopixel POV Display

To test the POV Display we used some double-sided tape to temporarily stick the aluminum bar to the table and we are powering the motor with 5V power from our regulated power supply. The image below displays our test setup.

Arduino POV Display

Once the hardware setup was done, we opened our web server app that we have previously built and tested different animations and effects. A collage image of some of those is shown below-

POV Displays with NeoPixel LEDS

With this, we are concluding our project on POV Display with WS2812B RGB LEDs. Hope you have enjoyed reading this and learned something useful. Now it’s time for you to build it on your own and let us know what you are doing with this project in the comment section below. If you have any questions or problems feel free to post them on our forums and we will get back to you.

Code

// Load Wi-Fi library, Fast-LED Library and Custom OTA Library

#include <ESP8266WiFi.h>

#include <FastLED.h>

#include "OTA.h"

 

#define LED_PIN     0 // LED is connected to the IO-0 of the ESP8266-01

#define NUM_LEDS    8 // We have 8 LEDs in the strip

#define BRIGHTNESS  50 // set the brightness for the LEDs

#define LED_TYPE    WS2812 // We are using WS2812B LEDs

#define COLOR_ORDER GRB // Colour order of the LEDs

#define UPDATES_PER_SECOND 100 // FPS

 

CRGB leds[NUM_LEDS]; // Create LED array for manipulation

CRGBPalette16 currentPalette; // set color palet type

TBlendType    currentBlending;// set blend mode

 

extern CRGBPalette16 myRedWhiteBluePalette; // create object for different palet

extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;

 

// Set web server port number to 80

WiFiServer server(80);

 

// variables to store the current Button state

String Animation_1 = "Deactivate";

String Animation_2 = "Deactivate";

String Animation_3 = "Deactivate";

 

// Variable to store the HTTP request

String header;

 

const char* ssid = "Semicon Media 2.4";

const char* password = "cdfiP29to665";

 

unsigned long currentTime = millis();

// Previous time

unsigned long previousTime = 0;

// Define timeout time in milliseconds (example: 2000ms = 2s)

const long timeoutTime = 2000;

 

 

void setup() {

  Serial.begin(115200);

  Serial.println("Booting");

 

  setupOTA("POV_Display", ssid , password);

 

  delay( 300 ); // power-up safety delay

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );

  FastLED.setBrightness(  BRIGHTNESS );

 

  currentPalette = RainbowColors_p;

  currentBlending = LINEARBLEND;

  server.begin();

}

 

// List of patterns to cycle through.  Each is defined as a separate function below.

typedef void (*SimplePatternList[])();

SimplePatternList gPatterns = { rainbow, rainbowWithGlitter, confetti, sinelon, juggle, bpm };

 

uint8_t gCurrentPatternNumber = 0; // Index number of which pattern is current

uint8_t gHue = 0; // rotating "base color" used by many of the patterns

 

 

void loop() {

 

  while ((unsigned long) millis() < 10000)

  {

    ArduinoOTA.handle();

    delay(10);

  }

  ArduinoOTA.handle();

 

  WiFiClient client = server.available();   // Listen for incoming clients

 

 

  if (client) {                             // If a new client connects,

    currentTime = millis();

    previousTime = currentTime;

    Serial.println("New Client");          // print a statement so the user knows that a new client got connected

    String incomingData = "";                // make a String to hold incoming data from client

 

    while (client.connected() && currentTime - previousTime <= timeoutTime) {  // client connects and request the page from server

      currentTime = millis();

      if (client.available()) {             // if client sends a byte to read

        char read_byte = client.read();             // read that byte from client

        Serial.write(read_byte);                    // print it to the serial monitor for debugging

        header += read_byte;                                // Store the bit stream for debugging

        if (read_byte == '\n') {                    // if the byte is a newline character

 

          // if you got two newline characters simultaneously. we can say its the end of the HTTP Request

          if (incomingData.length() == 0) {

            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)

            // and a content-type so the client knows what's coming, then a blank line:

            client.println("HTTP/1.1 200 OK");

            client.println("Content-type:text/html");

            client.println("Connection: close");

            client.println();

 

            //Set Different Mode For the LEDs

            if (header.indexOf("GET /Animation_1/Activate") >= 0) {

              // Serial.println("Animation 1 on");

              Animation_1 = "Activate";

 

            } else if (header.indexOf("GET /Animation_1/Deactivate") >= 0) {

              // Serial.println("Animation 1 off");

              Animation_1 = "Deactivate";

 

            } else if (header.indexOf("GET /Animation_2/Activate") >= 0) {

              // Serial.println("Animation 2 on");

              Animation_2 = "Activate";

 

            } else if (header.indexOf("GET /Animation_2/Deactivate") >= 0) {

              // Serial.println("Animation 2 off");

              Animation_2 = "Deactivate";

 

            } else if (header.indexOf("GET /Animation_3/Activate") >= 0) {

              // Serial.println("Animation 3 on");

              Animation_3 = "Activate";

 

            } else if (header.indexOf("GET /Animation_3/Deactivate") >= 0) {

              // Serial.println("Animation 3 off");

              Animation_3 = "Deactivate";

            }

 

 

 

            client.println("<!DOCTYPE html><html>");

            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");

            client.println("<link rel=\"icon\" href=\"data:,\">");

            client.println("<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}");

            client.println(".button { background-color: #3bc041; color: #ffffff; padding: 9px 30px ; text-decoration: none; font-size: 30px; margin: 2px ; border-radius: 5px ; border: 1px solid #ddffdf; box-shadow: 1px 2px 3px #0000001c; ");

            client.println("text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}");

            client.println(".button:hover{ background-color: #31b237; }");

            client.println(".button2 { background-color: #aaaaaa; border: 1px solid #bbbbbb; }</style></head>");

 

            // This is the heading of the webpage

            client.println("<body><h1>POV Display Controller</h1>");

 

            // Display current state, of the button 1

            client.println("<p>Pattern 1 - State " + Animation_1 + "</p>");

 

            if (Animation_1 == "Deactivate")

              client.println("<p><a href=\"/Animation_1/Activate\"><button class=\"button\">Activate</button></a></p>");

            else

              client.println("<p><a href=\"/Animation_1/Deactivate\"><button class=\"button button2\">Deactivate</button></a></p>");

 

 

            // Display current state, of the button 2

            client.println("<p>Pattern 2 - State " + Animation_2 + "</p>");

 

            if (Animation_2 == "Deactivate")

              client.println("<p><a href=\"/Animation_2/Activate\"><button class=\"button\">Activate</button></a></p>");

            else

              client.println("<p><a href=\"/Animation_2/Deactivate\"><button class=\"button button2\">Deactivate</button></a></p>");

 

 

            // Display current state, of the button 3

            client.println("<p>Pattern 3 - State " + Animation_3 + "</p>");

 

            if (Animation_3 == "Deactivate")

              client.println("<p><a href=\"/Animation_3/Activate\"><button class=\"button\">Activate</button></a></p>");

            else

              client.println("<p><a href=\"/Animation_3/Deactivate\"><button class=\"button button2\">Deactivate</button></a></p>");

 

 

            client.println("</body></html>");

 

            // The HTTP response ends with another blank line

            client.println();

 

            break;

          }

          else  // if you got a newline, then clear incomingData

            incomingData = "";

 

        }

 

        else if (read_byte != '\r')   // if you got anything else but a carriage return character,

          incomingData += read_byte;      // add it to the end of the currentLine

 

      }

    }

    // Clear the header variable

    header = "";

    // Close the connection

    client.stop();

    Serial.println("Client disconnected");

    Serial.println("\n");

  }

 

  if (Animation_1 == "Activate")

  {

    color_palette();

  }

 

  else if (Animation_2 == "Activate")

  {

    animation();

    //cyclone_colour();

  }

 

  else if (Animation_3 == "Activate")

  {

    //first_light();

    //demo_reel100();

    for (int i = 0; i < 8; i++)

      leds[i] = CRGB::Red;

    FastLED.show();

    delay(5);

   

    for (int i = 0; i < 8; i++)

      leds[i] = CRGB::Green;

    FastLED.show();

    delay(5);

 

    for (int i = 0; i < 8; i++)

      leds[i] = CRGB::White;

    FastLED.show();

    delay(5);

 

  }

 

  else if (Animation_1 == "Deactivate" && Animation_2 == "Deactivate" && Animation_3 == "Deactivate")

  {

    for (int i = 0; i < 8; i++)

    {

      leds[i] = CRGB::Black;

      FastLED.show();

      delay(2);

    }

  }

 

}

 

 

void color_palette()

{

  Serial.println("working");

  ChangePalettePeriodically();

  static uint8_t startIndex = 0;

  startIndex = startIndex + 1; /* motion speed */

  FillLEDsFromPaletteColors( startIndex);

  FastLED.show();

  FastLED.delay(1000 / UPDATES_PER_SECOND);

}

void cyclone_colour()

{

  static uint8_t hue = 0;

  Serial.print("x");

  // First slide the led in one direction

  for (int i = 0; i < NUM_LEDS; i++) {

    // Set the i'th led to red

    leds[i] = CHSV(hue++, 255, 255);

    // Show the leds

    FastLED.show();

    // now that we've shown the leds, reset the i'th led to black

    // leds[i] = CRGB::Black;

    fadeall();

    // Wait a little bit before we loop around and do it again

    delay(10);

  }

  Serial.print("x");

 

  // Now go in the other direction.

  for (int i = (NUM_LEDS) - 1; i >= 0; i--) {

    // Set the i'th led to red

    leds[i] = CHSV(hue++, 255, 255);

    // Show the leds

    FastLED.show();

    // now that we've shown the leds, reset the i'th led to black

    // leds[i] = CRGB::Black;

    fadeall();

    // Wait a little bit before we loop around and do it again

    delay(10);

  }

 

}

 

 

void animation()

{

  EVERY_N_MILLISECONDS( 20) {

    pacifica_loop();

    FastLED.show();

  }

}

void fadeall() {

  for (int i = 0; i < NUM_LEDS; i++) {

    leds[i].nscale8(250);

  }

}

 

void first_light()

{

  // Move a single white led

  for (int whiteLed = 0; whiteLed < NUM_LEDS; whiteLed = whiteLed + 1) {

    // Turn our current led on to white, then show the leds

    leds[whiteLed] = CRGB::White;

 

    // Show the leds (only one of which is set to white, from above)

    FastLED.show();

 

    // Wait a little bit

    delay(100);

 

    // Turn our current led back to black for the next loop around

    leds[whiteLed] = CRGB::Black;

  }

}

void demo_reel100()

{

  gPatterns[gCurrentPatternNumber]();

 

  // send the 'leds' array out to the actual LED strip

  FastLED.show();

  // insert a delay to keep the framerate modest

  FastLED.delay(1000 / UPDATES_PER_SECOND);

 

  // do some periodic updates

  EVERY_N_MILLISECONDS( 20 ) {

    gHue++;  // slowly cycle the "base color" through the rainbow

  }

  EVERY_N_SECONDS( 10 ) {

    nextPattern();  // change patterns periodically

  }

}

void FillLEDsFromPaletteColors( uint8_t colorIndex)

{

  uint8_t brightness = 255;

 

  for ( int i = 0; i < NUM_LEDS; ++i) {

    leds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);

    colorIndex += 3;

  }

}

 

 

 

void rgb_wheel()

{

  for (int i = 0; i < 8; i++)

    leds[i] = CRGB::Red;

  FastLED.show();

  delay(5);

  for (int i = 0; i < 8; i++)

    leds[i] = CRGB::Blue;

  FastLED.show();

  delay(5);

 

  for (int i = 0; i < 8; i++)

    leds[i] = CRGB::Green;

  FastLED.show();

  delay(5);

  for (int i = 0; i < 8; i++)

    leds[i] = CRGB::Black;

  FastLED.show();

}

void ChangePalettePeriodically()

{

  uint8_t secondHand = (millis() / 1000) % 60;

  static uint8_t lastSecond = 99;

 

  if ( lastSecond != secondHand) {

    lastSecond = secondHand;

    if ( secondHand ==  0)  {

      currentPalette = RainbowColors_p;

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 10)  {

      currentPalette = RainbowStripeColors_p;

      currentBlending = NOBLEND;

    }

    if ( secondHand == 15)  {

      currentPalette = RainbowStripeColors_p;

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 20)  {

      SetupPurpleAndGreenPalette();

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 25)  {

      SetupTotallyRandomPalette();

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 30)  {

      SetupBlackAndWhiteStripedPalette();

      currentBlending = NOBLEND;

    }

    if ( secondHand == 35)  {

      SetupBlackAndWhiteStripedPalette();

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 40)  {

      currentPalette = CloudColors_p;

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 45)  {

      currentPalette = PartyColors_p;

      currentBlending = LINEARBLEND;

    }

    if ( secondHand == 50)  {

      currentPalette = myRedWhiteBluePalette_p;

      currentBlending = NOBLEND;

    }

    if ( secondHand == 55)  {

      currentPalette = myRedWhiteBluePalette_p;

      currentBlending = LINEARBLEND;

    }

  }

}

 

// This function fills the palette with totally random colors.

void SetupTotallyRandomPalette()

{

  for ( int i = 0; i < 16; ++i) {

    currentPalette[i] = CHSV( random8(), 255, random8());

  }

}

 

// This function sets up a palette of black and white stripes,

// using code.  Since the palette is effectively an array of

// sixteen CRGB colors, the various fill_* functions can be used

// to set them up.

void SetupBlackAndWhiteStripedPalette()

{

  // 'black out' all 16 palette entries...

  fill_solid( currentPalette, 16, CRGB::Black);

  // and set every fourth one to white.

  currentPalette[0] = CRGB::White;

  currentPalette[4] = CRGB::White;

  currentPalette[8] = CRGB::White;

  currentPalette[12] = CRGB::White;

 

}

 

// This function sets up a palette of purple and green stripes.

void SetupPurpleAndGreenPalette()

{

  CRGB purple = CHSV( HUE_PURPLE, 255, 255);

  CRGB green  = CHSV( HUE_GREEN, 255, 255);

  CRGB black  = CRGB::Black;

 

  currentPalette = CRGBPalette16(

                     green,  green,  black,  black,

                     purple, purple, black,  black,

                     green,  green,  black,  black,

                     purple, purple, black,  black );

}

 

 

// This example shows how to set up a static color palette

// which is stored in PROGMEM (flash), which is almost always more

// plentiful than RAM.  A static PROGMEM palette like this

// takes up 64 bytes of flash.

const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM =

{

  CRGB::Red,

  CRGB::Gray, // 'white' is too bright compared to red and blue

  CRGB::Blue,

  CRGB::Black,

 

  CRGB::Red,

  CRGB::Gray,

  CRGB::Blue,

  CRGB::Black,

 

  CRGB::Red,

  CRGB::Red,

  CRGB::Gray,

  CRGB::Gray,

  CRGB::Blue,

  CRGB::Blue,

  CRGB::Black,

  CRGB::Black

};

 

 

#define ARRAY_SIZE(A) (sizeof(A) / sizeof((A)[0]))

 

void nextPattern()

{

  // add one to the current pattern number, and wrap around at the end

  gCurrentPatternNumber = (gCurrentPatternNumber + 1) % ARRAY_SIZE( gPatterns);

}

 

void rainbow()

{

  // FastLED's built-in rainbow generator

  fill_rainbow( leds, NUM_LEDS, gHue, 7);

}

 

void rainbowWithGlitter()

{

  // built-in FastLED rainbow, plus some random sparkly glitter

  rainbow();

  addGlitter(80);

}

 

void addGlitter( fract8 chanceOfGlitter)

{

  if ( random8() < chanceOfGlitter) {

    leds[ random16(NUM_LEDS) ] += CRGB::White;

  }

}

 

void confetti()

{

  // random colored speckles that blink in and fade smoothly

  fadeToBlackBy( leds, NUM_LEDS, 10);

  int pos = random16(NUM_LEDS);

  leds[pos] += CHSV( gHue + random8(64), 200, 255);

}

 

void sinelon()

{

  // a colored dot sweeping back and forth, with fading trails

  fadeToBlackBy( leds, NUM_LEDS, 20);

  int pos = beatsin16( 13, 0, NUM_LEDS - 1 );

  leds[pos] += CHSV( gHue, 255, 192);

}

 

void bpm()

{

  // colored stripes pulsing at a defined Beats-Per-Minute (BPM)

  uint8_t BeatsPerMinute = 62;

  CRGBPalette16 palette = PartyColors_p;

  uint8_t beat = beatsin8( BeatsPerMinute, 64, 255);

  for ( int i = 0; i < NUM_LEDS; i++) { //9948

    leds[i] = ColorFromPalette(palette, gHue + (i * 2), beat - gHue + (i * 10));

  }

}

 

void juggle() {

  // eight colored dots, weaving in and out of sync with each other

  fadeToBlackBy( leds, NUM_LEDS, 20);

  uint8_t dothue = 0;

  for ( int i = 0; i < 8; i++) {

    leds[beatsin16( i + 7, 0, NUM_LEDS - 1 )] |= CHSV(dothue, 200, 255);

    dothue += 32;

  }

}

 

CRGBPalette16 pacifica_palette_1 =

{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,

  0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x14554B, 0x28AA50

};

CRGBPalette16 pacifica_palette_2 =

{ 0x000507, 0x000409, 0x00030B, 0x00030D, 0x000210, 0x000212, 0x000114, 0x000117,

  0x000019, 0x00001C, 0x000026, 0x000031, 0x00003B, 0x000046, 0x0C5F52, 0x19BE5F

};

CRGBPalette16 pacifica_palette_3 =

{ 0x000208, 0x00030E, 0x000514, 0x00061A, 0x000820, 0x000927, 0x000B2D, 0x000C33,

  0x000E39, 0x001040, 0x001450, 0x001860, 0x001C70, 0x002080, 0x1040BF, 0x2060FF

};

 

 

void pacifica_loop()

{

  // Increment the four "color index start" counters, one for each wave layer.

  // Each is incremented at a different speed, and the speeds vary over time.

  static uint16_t sCIStart1, sCIStart2, sCIStart3, sCIStart4;

  static uint32_t sLastms = 0;

  uint32_t ms = GET_MILLIS();

  uint32_t deltams = ms - sLastms;

  sLastms = ms;

  uint16_t speedfactor1 = beatsin16(3, 179, 269);

  uint16_t speedfactor2 = beatsin16(4, 179, 269);

  uint32_t deltams1 = (deltams * speedfactor1) / 256;

  uint32_t deltams2 = (deltams * speedfactor2) / 256;

  uint32_t deltams21 = (deltams1 + deltams2) / 2;

  sCIStart1 += (deltams1 * beatsin88(1011, 10, 13));

  sCIStart2 -= (deltams21 * beatsin88(777, 8, 11));

  sCIStart3 -= (deltams1 * beatsin88(501, 5, 7));

  sCIStart4 -= (deltams2 * beatsin88(257, 4, 6));

 

  // Clear out the LED array to a dim background blue-green

  fill_solid( leds, NUM_LEDS, CRGB( 2, 6, 10));

 

  // Render each of four layers, with different scales and speeds, that vary over time

  pacifica_one_layer( pacifica_palette_1, sCIStart1, beatsin16( 3, 11 * 256, 14 * 256), beatsin8( 10, 70, 130), 0 - beat16( 301) );

  pacifica_one_layer( pacifica_palette_2, sCIStart2, beatsin16( 4,  6 * 256,  9 * 256), beatsin8( 17, 40,  80), beat16( 401) );

  pacifica_one_layer( pacifica_palette_3, sCIStart3, 6 * 256, beatsin8( 9, 10, 38), 0 - beat16(503));

  pacifica_one_layer( pacifica_palette_3, sCIStart4, 5 * 256, beatsin8( 8, 10, 28), beat16(601));

 

  // Add brighter 'whitecaps' where the waves lines up more

  pacifica_add_whitecaps();

 

  // Deepen the blues and greens a bit

  pacifica_deepen_colors();

}

 

// Add one layer of waves into the led array

void pacifica_one_layer( CRGBPalette16& p, uint16_t cistart, uint16_t wavescale, uint8_t bri, uint16_t ioff)

{

  uint16_t ci = cistart;

  uint16_t waveangle = ioff;

  uint16_t wavescale_half = (wavescale / 2) + 20;

  for ( uint16_t i = 0; i < NUM_LEDS; i++) {

    waveangle += 250;

    uint16_t s16 = sin16( waveangle ) + 32768;

    uint16_t cs = scale16( s16 , wavescale_half ) + wavescale_half;

    ci += cs;

    uint16_t sindex16 = sin16( ci) + 32768;

    uint8_t sindex8 = scale16( sindex16, 240);

    CRGB c = ColorFromPalette( p, sindex8, bri, LINEARBLEND);

    leds[i] += c;

  }

}

 

// Add extra 'white' to areas where the four layers of light have lined up brightly

void pacifica_add_whitecaps()

{

  uint8_t basethreshold = beatsin8( 9, 55, 65);

  uint8_t wave = beat8( 7 );

 

  for ( uint16_t i = 0; i < NUM_LEDS; i++) {

    uint8_t threshold = scale8( sin8( wave), 20) + basethreshold;

    wave += 7;

    uint8_t l = leds[i].getAverageLight();

    if ( l > threshold) {

      uint8_t overage = l - threshold;

      uint8_t overage2 = qadd8( overage, overage);

      leds[i] += CRGB( overage, overage2, qadd8( overage2, overage2));

    }

  }

}

 

// Deepen the blues and greens

void pacifica_deepen_colors()

{

  for ( uint16_t i = 0; i < NUM_LEDS; i++) {

    leds[i].blue = scale8( leds[i].blue,  145);

    leds[i].green = scale8( leds[i].green, 200);

    leds[i] |= CRGB( 2, 5, 7);

  }

}

Video

Have any question realated to this Article?

Ask Our Community Members