Changing LED Color


#1

I’d like to add a push button to my lightsaber that allows me to cycle through various blade color options. (I’d also like to add the initialization sounds upon switching colors, and add color retention to the memory so when I turn the lightsaber off and on again, the same color will come up; but baby steps.) I was hoping this would be a doable modification to the code, but after trying it, I’m remembering why I didn’t become a programmer!

I’m using the default lightsaber code (with a couple of minor tweaks, thanks in part to Ivan), and my wiring scheme is identical to the tutorial, except that I’m not using an LED-equipped on/off button. This frees pin 3 and the second ground connection for my color-cycling push button.

Step 1 in this process is making the blade LED change to a different color when I press the color-cycling button. Right now, I can’t even get that to work. The PropButton Library page isn’t very clear on when to use “if” statements vs the “If pressed” and “If released” statements. I also don’t follow the Active-low/active-high logic.

Right now, the default startup color is blue. What I’m looking to do is basically the following:
If ColorButton is pressed
Change LED color to redShimmer (and play an initialization sound)
If it’s pressed again
Change LED color to Greenshimmer
If it’s pressed again
etc etc

Any advice would be helpful.


#2

Hi @Joecool,

Alright, let’s divide the question into parts:

I’m currently working of version 1.0.3 that includes a library to save configurations to the SD. As soon as we release some new products I can get back to it and release it.

This depends on how you connect your button.

The button has two contacts. One contact is connected to the 0-12 pins. Let’s say pin 3 for this example. The other contact can be connected to either ground or 3.3V.

If you connect the button to 3.3V, the button will be activated with a logic 1. That is, when the button is pressed, the pin 3 will be connected to 3.3V through the button. This is called “active high”.

If you connect the button to ground, when the button is pressed the pin 3 will be connected to ground. This is called “active low”. For this to happen (to detect a transition from logic level 1 to logic level 0) the pin 3 has to be first connected to 3.3V through an internal pull-up resistor. This is done automatically by the software when you call PropButton::begin.

Why all this trouble? Well, there is a single 3.3V pin on the board, and there are plenty of places to get a ground connection (for example, the battery). Connecting the buttons to ground can simplify the wiring of your prop.

I should think a way to put it more clear in the documentation.

Please, post the portion of code you think it could be the problem.

Let me see if we can work on some code to do the job. Note that the code here below is an example and is not tested but can be used as a guide. It should be added to the code that you already have.

PropButton colorChangeButton;    // The button for color changing
WavPlayer colorChangerSound;    // The player to play a sound when color changes

void setup()
{
    // Other code here

    // Initialize the button for color change
    colorChangeButton.begin(3, ButtonActiveLow); // Active-low button on pin 3

    // Other code here
}

void loop()
{
    ButtonEvent button_event;

    // Check the current state
    switch(state)
    {
        // Other code here
        
        case STATE_IDLE:
            // This is the main state. The hum sound is looping in the background
            // while we monitor for motion and buttons events.

            // Check if the colorChangeButton was pressed and released
            button_event = colorChangeButton.getEvent();

            if (button_event == ButtonShortPressAndRelease)
                // Button pressed and released. Call the color changing function.
                changeColor();

        // Other code here
    }
}

uint8_t color_combination = 1;

void changeColor()
{
    // Play a sound
    colorChangerSound.play("color_change.wav");

    // Check which color combination we should show
    switch(color_combination)
    {
        case 1:    // red
            // Shimmer from 220 to 255 and back with a 2Hz frequency
            red.shimmer(220, 255, 2, 0);
            green.setValue(0);
            blue.setValue(0);
            break;

        case 2:    // green
            // Shimmer from 220 to 255 and back with a 2Hz frequency
            green.shimmer(220, 255, 2, 0);
            red.setValue(0);
            blue.setValue(0);
            break;

        case 3:    // blue
            // Shimmer from 220 to 255 and back with a 2Hz frequency
            blue.shimmer(220, 255, 2, 0);
            red.setValue(0);
            green.setValue(0);
            break;
    }

    color_combination++;
    if (color_combination == 4)
        color_combination = 1;
}

The above code should play a sound and cycle between red, green and blue shimmering when you press the button. Obviously you can change the blue, red, and green shimmer values as you like, or add more combinations. Or perhaps adding some sort of transition between colors.

Let me know if something was not clear or if you need further advice.


#3

Thank you, Ivan. That explanation of ButtonActiveLow and ButtonActiveHigh is hugely helpful! I’d strongly recommend its inclusion in the website tutorial.

And thank you so much for that code!!! I’ve inserted it in I believe the correct locations, but the color-changing button isn’t doing anything. My guess is that’s because I haven’t initialized the button using the uint32_t command? (The PropButton::Begin command is a little confusing to me because I don’t see the syntax listed ever being used in an example.)

Here’s what I’m thinking of listing near the top of the code (before/outside any brackets):

Void begin(uint32_t 3, ButtonType type = ButtonActiveLow, uint32_t minimum_debouncer = 25);

My button is connected from Pin 3 to ground, and the 25ms debouncer is fine to start with.

When I try and verify this code, the software says “expected ‘,’ or ‘…’ before numeric constant”. of course, adding that doesn’t fix the problem. Any idea where I’m going wrong?


#4

Hi @Joecool,

The code from my previous post already contains the initialization of the button.

PropButton is an object that contains generic code to work with all your buttons. It’s done that way so you can reuse the code inside the object to work with buttons connected to different pins. When you do:

PropButton colorChangeButton;    // The button for color changing

You are creating a button called colorChangeButton. And when you do this:

colorChangeButton.begin(3, ButtonActiveLow); // Active-low button on pin 3

You are initializing the colorChangeButton on pin 3 in ButtonActiveLow mode. That begin here above is the one you see in the documentation, but redacted to in a way to explain its syntax.

The same applies to objects like WavPlayer, LedStrip or HBLED.

Here is the whole code from the example with the modifications from the other post. It compiles, but I didn’t tested it. I’ve also modified the ledRampDown, ledRampUp and ledShimmer functions to follow the current color combination. As is, the color combinations are pure red, pure green and pure blue. You can later modify said functions to do other color combinations.

#include <PropButton.h>

// Button pins
#define BUTTON_PIN      1
#define BUTTON_LED_PIN  2

// Lightsaber states
#define STATE_OFF       0
#define STATE_POWER_ON  1
#define STATE_IDLE      2
#define STATE_POWER_OFF 3
#define STATE_HIT       4
#define STATE_BLASTER   5

// Blade color combination
#define BLADE_RED       1
#define BLADE_GREEN     2
#define BLADE_BLUE      3

// Five minutes before entering low power
#define TIME_BEFORE_LOW_POWER 300000

// Red LED on output 1
HBLED red(1);
// Green LED on output 2
HBLED green(2);
// Blue LED on output 3
HBLED blue(3);

// One WAV player for the hum sound, one for the sound effects
WavPlayer hum, fx;

// The player to play a sound when color changes
WavPlayer colorChangerSound;

// Our push button with LED
PropButton OnOffButton;

// The button for color changing
PropButton colorChangeButton;

// This variable holds the current color combination,
// starting with RED
uint8_t colorCombination = BLADE_RED;

// The current state
uint8_t state = STATE_OFF;

// Two booleans to signal motion events
volatile bool on_swing = false;
volatile bool on_hit = false;

// This variable holds the time (millis() ticks)
// since the program in the STATE_OFF state.
uint32_t time_off;

void setup()
{
  // Initialize audio
  Audio.begin();

  // Initialize motion sensor
  Motion.begin();
  Motion.configPulse(AxisAll, 3.78f, 500, 100, MotionInterrupt1 );
  Motion.configTransient(AxisAll, 0.1f, 120, MotionInterrupt2);
  Motion.attachInterrupt(MotionInterrupt1, MotionPulse);
  Motion.attachInterrupt(MotionInterrupt2, MotionTransient);

  // Initialize LEDs
  blue.begin(700);  // Blue LED with 700mA maximum current
  red.begin(700);   // Red LED with 700mA maximum current
  green.begin(700); // Green LED 700mA maximum current

  // Initialize On/Off button
  OnOffButton.begin(BUTTON_PIN, ButtonActiveLow);

  // Initialize the button for color change
  colorChangeButton.begin(3, ButtonActiveLow); // Active-low button on pin 3

  // Configure the OnOffButton to declare a long press after 1000ms
  OnOffButton.setLongPressTime(1000);

  // Initial blinking LED sequence, cycle: 2000ms, time on: 100ms
  OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);

  // We entered in the STATE_OFF state
  time_off = millis();
}

void loop()
{
  ButtonEvent button_event;

  // Check the current state
  switch(state)
  {
    case STATE_OFF:
      // If On/Off button is pressed
      if (OnOffButton.pressed())
      {
        // New blinking sequence, cycle: 500ms, time on: 100ms
        OnOffButton.blink(BUTTON_LED_PIN, 500, 100);

        // Wait for the user to release the OnOffButton
        while (OnOffButton.pressed());

        // Go to STATE_POWER_ON
        state = STATE_POWER_ON;
      } else {
        // Check if we have to go into low-power
        if (millis() - time_off >= TIME_BEFORE_LOW_POWER)
        {
          // Stop blinking the button LED
          OnOffButton.blink(BUTTON_LED_PIN, 0, 0);

          // Enter low power mode. Wait for a FALLING edge
          // on the button pin.
          enterLowPowerMode(BUTTON_PIN, FALLING);
        }
      }
      break;

    case STATE_POWER_ON:

      // Play ignition sound
      fx.play("on0.wav");

      // Ramp LEDs up
      ledRampUp(fx.duration());

      // Wait a little before launching the hum sound
      delay(500);

      // Play hum sound in loop mode
      hum.play("idle1.wav", PlayModeLoop);

      // Wait until ignition sound finishes
      while (fx.playing());

      // Start shimmering LEDs
      ledShimmer();

      // Check if user still pressing the On/Off button. If so, wait.
      while (OnOffButton.pressed());

      // Ignore any motion detected previous to STATE_IDLE
      on_swing = on_hit = false;

      // Clear any OnOffButton queued events
      OnOffButton.resetEvents();

      // Go to STATE_IDLE
      state = STATE_IDLE;
      break;

    case STATE_IDLE:
      // This is the main state. The hum sound is looping in the background
      // while we monitor for motion and buttons events.

      button_event = colorChangeButton.getEvent();

      if (button_event == ButtonShortPressAndRelease)
        // Button pressed and released. Call the color changing function.
        changeColor();

      button_event = OnOffButton.getEvent();
        
      // If On/Off button is long-pressed, go to STATE_POWER_OFF.
      if (button_event == ButtonLongPressed)
      {
        // Go back to initial blinking sequence
        OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);
        state = STATE_POWER_OFF;
        break;
      }

      // If OnOffButton is pressed and released, go to STATE_BLASTER.
      if (button_event == ButtonShortPressAndRelease)
      {
        state = STATE_BLASTER;
        break;
      }

      // Check hits
      if (on_hit)
      {
        on_hit = 0;
        state = STATE_HIT;
        break;
      }

      // Check swings
      if (on_swing)
      {
        on_swing = 0;

        // Play a random swing sound (from swing0.wav to swing7.wav)
        fx.playRandom("swing", 0, 7);
      }
      break;

    case STATE_HIT:
      // Flash the LEDs
      ledFlash();

      // Play a random strike sound (from strike0.wav to strike2.wav)
      fx.playRandom("strike", 0, 2);
      while (fx.playing())
      {
        // Check if another clash happened while we were playing the sound
        if (on_hit)
        {
          on_hit = 0;

          // Play again
          fx.playRandom("strike", 0, 2);
        }
      }

      // Go back to IDLE shimmering and to STATE_IDLE state
      ledShimmer();
      state = STATE_IDLE;
      break;

    case STATE_BLASTER:
      // Blaster hit, start flashing the LEDs
      ledFlash();

      // Play a random blaster sound (from hit0.wav to hit4.wav)
      fx.playRandom("hit", 0, 4);

      while (fx.playing())
      {
        // Check if the OnOffButton was pressed againg while playing the blaster sound
        if (OnOffButton.getEvent() == ButtonShortPressAndRelease)
          // Play again
          fx.playRandom("hit", 0, 4, PlayModeNormal);
      }

      // No more blasters, stop flashing and go back to STATE_IDLE
      ledShimmer();
      state = STATE_IDLE;
      break;

    case STATE_POWER_OFF:
      // Stop the hum sound
      hum.stop();

      // Play the OFF sound
      fx.play("off0.wav");

      // Ramp the LEDs down
      ledRampDown(fx.duration());

      while (fx.playing());

      // Wait until OnOffButton is released
      while (OnOffButton.pressed());

      // Initial blinking LED sequence, cycle: 2000ms, time on: 100ms
      OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);

      // Remember when we entered in the STATE_OFF state
      time_off = millis();

      // Go back to OFF state
      state = STATE_OFF;
      break;
  }
}

// Motion transient interrupt (swing)
void MotionTransient()
{
  on_swing = true;
}

// Motion pulse interrupt (hit)
void MotionPulse()
{
  on_hit = true;
}

void ledShimmer()
{
  // Check which color combination we should show
  switch(colorCombination)
  {
    case BLADE_RED:    // red
        // Shimmer from 220 to 255 and back with a 2Hz frequency
        red.shimmer(220, 255, 2, 0);

        // Turn off the other LEDS
        green.setValue(0);
        blue.setValue(0);
        break;

    case BLADE_GREEN:    // green
        // Shimmer from 220 to 255 and back with a 2Hz frequency
        green.shimmer(220, 255, 2, 0);

        // Turn off the other LEDS
        red.setValue(0);
        blue.setValue(0);
        break;

    case BLADE_BLUE:    // blue
        // Shimmer from 220 to 255 and back with a 2Hz frequency
        blue.shimmer(220, 255, 2, 0);

        // Turn off the other LEDS
        red.setValue(0);
        green.setValue(0);
        break;
  }
}

static void ledFlash()
{
  // Flash all LEDs from a value of 255 to 100, frequency 20Hz, infinite duration
  blue.flash(255, 100, 20, 0);
  red.flash(255, 100, 20, 0);
  green.flash(255, 100, 20, 0);
}

void ledRampUp(uint32_t duration)
{
  // Check which color combination we should ramp up
  switch(colorCombination)
  {
    case BLADE_RED:    // red
      // Red LED ramp up from 0 to 50
      red.ramp(0, 220, duration);
      break;

    case BLADE_GREEN:    // green
      // Green LED ramp up from 0 to 50
      green.ramp(0, 220, duration);
      break;

    case BLADE_BLUE:    // blue
      // Blue LED ramp up from 0 to 50
      blue.ramp(0, 220, duration);
      break;
  }
}

void ledRampDown(uint32_t duration)
{
  // Check which color combination we should ramp down
  switch(colorCombination)
  {
    case BLADE_RED:    // red
      // Ramp down the red LED from 255 to 0
      red.ramp(255, 0, duration);
      break;

    case BLADE_GREEN:    // green
      // Ramp down the green LED from 255 to 0
      green.ramp(255, 0, duration);
      break;

    case BLADE_BLUE:    // blue
      // Ramp down the blue LED from 255 to 0
      blue.ramp(255, 0, duration);
      break;
  }
}

void changeColor()
{
  // Play a sound
  colorChangerSound.play("color_change.wav");

  colorCombination++;
  if (colorCombination == 4)
      colorCombination = BLADE_RED;
      
  ledShimmer();
}

I know if you have none or little programming experience it can be shocking the first time you try some modification. But once you grasp it, you can make the PropBoard do many wonderful things. :grinning:

Since you can program the PropBoard from the Arduino IDE, you can learn more about programming from here, specially the Programming section.

Let me know how it goes.


#5

Thanks, Ivan. That Arduino link is very helpful in better understanding the functions!

I took a course in C++ in college, but that was over a decade ago. I still shutter when thinking of the different types of variables (boolean, etc.) and when to use each. (It was never made very clear to me.)

I read through your code to better understand the logic, and it makes a lot of sense for the most part. (I particularly enjoy the clever incrementing of colorCombination!)

I integrated your code, and it helped a lot, along with a face-slapping moment of mine: At first, pressing the button continued to do nothing. But then I saw that I had it wired to pin 2, not pin 3. Oops.

Once I corrected that in the code, I got a result, but a backwards one: The blade cycles at 1/2Hz between each color. It pauses whenever I press and hold the color-changing button. Pressing and releasing the button quickly (both at a rate greater than and less than 25ms) does nothing.

I think the problem lies with the fact that I have two buttons, but I’m not sure how. Perhaps the loop is confusing which button to check for an event? I.e., the button_event function is out of order? It doesn’t seem to be, but I’m kind of grasping at straws here. Another thought is that I just don’t quite understand how the ButtonEvent function works.

Here’s the current void loop:

void loop()
{
  ButtonEvent button_event;

  // Check the current state
  switch(state)
  {
    case STATE_OFF:
      // If On/Off button is pressed
      if (OnOffButton.pressed())
      {
        // New blinking sequence, cycle: 500ms, time on: 100ms
        OnOffButton.blink(BUTTON_LED_PIN, 500, 100);

        // Wait for the user to release the OnOffButton
        while (OnOffButton.pressed());

        // Go to STATE_POWER_ON
        state = STATE_POWER_ON;
      } else {
        // Check if we have to go into low-power
        if (millis() - time_off >= TIME_BEFORE_LOW_POWER)
        {
          // Stop blinking the button LED
          OnOffButton.blink(BUTTON_LED_PIN, 0, 0);

          // Enter low power mode. Wait for a RISING edge
          // on the button pin.
          enterLowPowerMode(BUTTON_PIN, RISING);
        }
      }
      break;

    case STATE_POWER_ON:

      // Play ignition sound
      fx.play("on0.wav");

      // Ramp LEDs up
      ledRampUp(fx.duration());

      // Wait a little before launching the hum sound
      delay(500);

      // Play hum sound in loop mode
      hum.play("idle1.wav", PlayModeLoop);

      // Wait until ignition sound finishes
      while (fx.playing());

      // Start shimmering LEDs
      ledShimmer();

      // Check if user still pressing the On/Off button. If so, wait.
      while (OnOffButton.pressed());

      // Ignore any motion detected previous to STATE_IDLE
      on_swing = on_hit = false;

      // Clear any OnOffButton queued events
      OnOffButton.resetEvents();

      // Go to STATE_IDLE
      state = STATE_IDLE;
      break;

    case STATE_IDLE:
      // This is the main state. The hum sound is looping in the background
      // while we monitor for motion and buttons events.
 
      button_event = ColorChangeButton.getEvent();
       if (button_event == ButtonShortPressAndRelease)
       {
         // Change Color
         changeColor();
         break;
       }

      button_event = OnOffButton.getEvent();

      // If On/Off button is long-pressed, go to STATE_POWER_OFF.
      if (button_event == ButtonLongPressed)
      {
        // Go back to initial blinking sequence
        OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);
        state = STATE_POWER_OFF;
        break;
      }

      // If OnOffButton is pressed and released, go to STATE_BLASTER.
      if (button_event == ButtonShortPressAndRelease)
      {
        state = STATE_BLASTER;
        break;
      }

      // Check hits
      if (on_hit)
      {
        on_hit = 0;
        state = STATE_HIT;
        break;
      }

      // Check swings
      if (on_swing)
      {
        on_swing = 0;

        // Play a random swing sound (from swing0.wav to swing7.wav)
        fx.playRandom("swing", 0, 7);
      }
      break;

    case STATE_HIT:
      // Flash the LEDs
      ledFlash();

      // Play a random strike sound (from strike0.wav to strike2.wav)
      fx.playRandom("strike", 0, 2);
      while (fx.playing())
      {
        // Check if another clash happened while we were playing the sound
        if (on_hit)
        {
          on_hit = 0;

          // Play again
          fx.playRandom("strike", 0, 2);
        }
      }

      // Go back to IDLE shimmering and to STATE_IDLE state
      ledShimmer();
      state = STATE_IDLE;
      break;

    case STATE_BLASTER:
      // Blaster hit, start flashing the LEDs
      ledFlash();

      // Play a random blaster sound (from hit0.wav to hit4.wav)
      fx.playRandom("hit", 0, 4);

      while (fx.playing())
      {
        // Check if the OnOffButton was pressed againg while playing the blaster sound
        if (OnOffButton.getEvent() == ButtonShortPressAndRelease)
          // Play again
          fx.playRandom("hit", 0, 4, PlayModeNormal);
      }

      // No more blasters, stop flashing and go back to STATE_IDLE
      ledShimmer();
      state = STATE_IDLE;
      break;

    case STATE_POWER_OFF:
      // Stop the hum sound
      hum.stop();

      // Play the OFF sound
      fx.play("off0.wav");

      // Ramp the LEDs down
      ledRampDown(fx.duration());

      while (fx.playing());

      // Wait until OnOffButton is released
      while (OnOffButton.pressed());

      // Initial blinking LED sequence, cycle: 2000ms, time on: 100ms
      OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);

      // Remember when we entered in the STATE_OFF state
      time_off = millis();

      // Go back to OFF state
      state = STATE_OFF;
      break;
  }
}

If I change the first button_event (with ColorChangeButton) from ButtonShortPressAndRelease to ButtonLongPressed, the color changing button no longer does anything, and the LED stays in the blue state. This seems to indicate to me that the problem lies with the two buttons getting mixed up somewhere, but I’m not sure where.

I tried adding a second “buttonEvent” called “button2_event” and changing the ColorChangeButton to only act when checking this button2_event, but there was no effect.

I also tried commenting out the entire ColorChangeButton.getEvent section, which caused the LED to stay blue and the color-changing button to not have any effect. This is evidence for the button mix-up, but I don’t know how to solve it.

I’ve tried a few other things as well, to no avail. Any ideas?


#6

Hi @Joecool,

I may have an idea of what’s going on. If you connected the button to the pin 2, check if the following line is still present in the code:

OnOffButton.blink(BUTTON_LED_PIN, 2000, 100);

If so, remove it. That would be the blinking LED for the on/off button. In the code it’s defined as pin 2, and it will confuse colorChangeButton since it will use the same pin as colorChangeButton and configure it as an output to toggle the LED.

Or solder the colorChangeButton to another pin.

The PropButton object monitors the button pin, de-bounces it, and collects buttons events in a queue (a FIFO). This happens in the background more or less every millisecond. The “queue” method is there so, for example, in case you missed a fast press-and-release event, you can detect it later.

Each PropButton object stores up to five events in the queue, represented by the ButtonEvent type, that can have one of the following values:

  • EventNone: no event was detected.
  • ButtonPressed: a button press has been detected
  • ButtonReleased: a button release has been detected
  • ButtonLongPressed: a button long-press has been detected
  • ButtonShortPressAndRelease: a button press followed by a button release was detected.

Every time you call getEvent, you pull one of said events from the queue (or EventNone if the queue is empty). If you are no longer interested in the events that may have happened, you can call resetEvents to clear the queue.

To know the actual button state, without pulling events from the queue, you can use the pressed and released functions.

If you want to know how it works, the PropButton internal code would be mainly this.


#7

Thank you, Ivan! I had to comment out OnOffButton.blink everywhere it appeared, but once I did, it finally worked!

Thank you so much for all your help. The info about PropButton is also very helpful. I might try playing with the different functions.

Now I can continue my Jedi training. :wink: