Software - Uberibsen/AstralisLEDSign GitHub Wiki

The code for running the WS2812B LED strip is made with the help of the FastLED library. The FastLED library is an easy and fast way to light the individual LED's up in the order you wish as well as generating lighting patterns.

The lighting effects are primarily based upon this website, demonstrating a handful of effects as well as the code and setup. These effects are based upon the concept of for loops and is a good way to run these effects. However, this project implements the addition of a button to change between effects, and this feature comes with one issue. The for loops 'occupy' the running code and only continues when the loop is done. Therefore, if you push the button while the for loop is running, the push will not register.

Because of this, the effects used have been rewritten in order to counter this issue by using global variables and reduce the number of for loops used.

The two examples shown below makes the same effect, but are written in two different ways. The first one is the original effect and does not register a button push while running, whereas the second example does because it is utilizing global variables, which can be used outside of the function theaterChase.

Example one:

void theaterChase(byte red, byte green, byte blue, int SpeedDelay) {
  for (int j=0; j<10; j++) {  //do 10 cycles of chasing
    for (int q=0; q < 3; q++) {
      for (int i=0; i < NUM_LEDS; i=i+3) {
        setPixel(i+q, red, green, blue);    //turn every third pixel on
      }
      showStrip();
     
      delay(SpeedDelay);
     
      for (int i=0; i < NUM_LEDS; i=i+3) {
        setPixel(i+q, 0,0,0);        //turn every third pixel off
      }
    }
  }
}

Example two:

int theaterDistance = 3;
int theaterDelay = 300;
int theaterJ = 0;
int theaterI = 0;

void theaterChase(byte red, byte green, byte blue) {
  if (theaterI == 0) {
    for (int i=0; i < NUM_LEDS; i++) {
      setPixel(i, 0,0,0);
    }

    for (int i=0; i < NUM_LEDS; i+=theaterDistance) {
      setPixel(i+theaterJ, red, green, blue);    //turn every theaterDistance pixel on
    }
    showStrip();
    theaterJ = (theaterJ + 1) % theaterDistance;
  }
  theaterI = (theaterI + 1) % theaterDelay;
}

The brightness of the LED's is controlled via a potentiometer, as discussed. The signal is read initially as a value from 0 to 1023. The FastLED library only works with numbers from 0 to 255 in order to define the brightness. The numbers will then need to be mapped with the map function, as shown below and then constrained with the variables MIN_BRIGHTNESS and MAX_BRIGHTNESS, which is used as limiters.

  int brightnessValue = map(analogRead(brightnessPin), 0, 1023, 0, 255);
  FastLED.setBrightness(constrain(brightnessValue, MIN_BRIGHTNESS, MAX_BRIGHTNESS));

As discussed in the Deboucing section in Hardware, buttons have a tendency to generate 'noice' when pressed and released. The code below checks twice in a short period of time to make sure the pushbutton is definitely pressed.

The button push hereafter increments sequenceNumber by one, and runs the lighting function corresponding to the number. It also clears any data that should be remaining in the LED's with setAll(0, 0, 0); and shifts the correct number to the 74HC595 to light up the corresponding sequence number on the 7-segment display.

 // LED lightning sequence control
  buttonState = digitalRead(buttonPin);

  if (buttonState != lastButtonState) {
    if (buttonState == HIGH) {
      sequenceNumber = (sequenceNumber+1) % 5;
      writeAndShift(sequenceNumber + 1);
      setAll(0, 0, 0);
    }
  }
  // save the current state as the last state, for next time through the loop
  lastButtonState = buttonState;

Finally, the Arduino needs to know which lighting sequence to run depending on the sequenceNumber variable. This is done by using switch and case statements. These work just like if statements but are much nicer to look at. switch(sequenceNumber) is used to define which declaration should be used for the case statements. The case 1: will run if sequenceNumber equals 1, case 2: if sequenceNumber equals 2 and so on. Under every case, you define what code should be executed.

 switch(sequenceNumber){

    case 0:
      FadeInOut(0xff, 0x00, 0x00); // Only using red
      break;

default: is used as a failsafe, should the value go beyond the given case statements. Much like the else statement, you define what should be executed, if none of the above statements are true.