Button Array Toggle

Hi Guys,

I’m having a challenging time on this project and think I may have bitten off more than I can chew, so I thought I’d ask some experts!

There are a few issues I’m facing but I thought I’d start at the beginning.

I have a series of seven buttons which I’ve managed to toggle on and off when I press them, what’s happening though is that I can still have multiple buttons that can be turned on at once. What I need is that only one button can be on at a time. So when that button is pressed, all the other ones turn off… I did try a few things but nothing seems to work.

Any help would be greatly appreciated!

Thanks,

Chris

// ------------------------------ LIBRARIES ------------------------------ //
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_NeoPixel.h>

// ------------------------------ DEFINITIONS ------------------------------ //

#define PIN 8 // data pin for NeoPixels
#define QTY 6 // quantity of NeoPixels

#define FADEINTERVAL 20 // the speed of the fade up of the LED
#define DELAYINT 10 // interval between each LED

Adafruit_MCP23017 mcp;
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(QTY, PIN, NEO_RGB + NEO_KHZ800);

// ------------------------------ DECLARATIONS ------------------------------ //

// State Counter declarations
byte currentState[8]; // momentary state of button push
byte lastState[8]; // recorded state of last button push
int counterState[8];
char colorString[8] = {'R', 'O', 'Y', 'G', 'B', 'I', 'V', 'W'};
char color;

// declarations for the Chase function
int interval = FADEINTERVAL;
byte delayint = DELAYINT;
unsigned long currenttime;
unsigned long previoustime;
int colorArrayR[] = {0, 2, 4};

//final value of bright LED
byte valR;
byte valG;
byte valB;
// value used in the map functions
byte timeR;
byte timeG;
byte timeB;

// ------------------------------ SETUP ------------------------------ //

void setup()
{
  // begin Serial transmission
  Serial.begin(115200);
  
  // begin NeoPixel code
  pixel.begin();
  pixel.show();
  
  // set the last 8 pins on the MCP to outputs to control the lights on the buttons
  mcp.begin();
  for (int b=8; b<16; b++) {
    mcp.pinMode(b,OUTPUT);
    mcp.digitalWrite(b,LOW);
  }
}

// ------------------------------ LOOP ------------------------------ //

void loop()
{
  // NeoPixel output
  pixel.show();
  
  // State Counter
  for (int a=0; a<8; a++) { // for loop that looks at the whole 8 buttons
    currentState[a] = mcp.digitalRead(a); // read the immediate state of the switch
    if (currentState[a] != lastState[a]) { // checks if the previous state and the current state are different
      if (currentState[a] == HIGH) {counterState[a]++;} // increments the counter
    }
    lastState[a] = currentState[a]; // sets the current state to the last state
    if (counterState[a] % 2 != 0) {blinker(15-a,2);} // if the button is evenly divisble by 2 then set the light on
    else {blinker(15-a,0);} // else set the light off
  }
  
  // Colour State
  color = 'N';
  for (int c=0; c<8; c++) {
    if (counterState[c] % 2 != 0) {
      color = colorString[c];
    }
  }
  
  
  // Switch Case
  switch (color)
  {
    case 'R':        // button 1 (RED)
      chase(100,0,0);
      break;
    case 'O':        // button 2 (ORANGE)
      chase(90,75,0);
      break;
    case 'Y':        // button 3 (YELLOW)
      chase(90,85,0);
      break;
    case 'G':        // button 4 (GREEN)
      chase(0,100,0);
      break;
    case 'B':       // button 5 (BLUE)
      chase(0,0,100);
      break;
    case 'I':       // button 6 (INDIGO)
      chase(85,0,100);
      break;
    case 'V':       // button 7 (VIOLET)
      chase(75,0,100);
      break;
    case 'W':      // button 8 (WHITE)
      for(int i=0; i<pixel.numPixels(); i++) 
      chase(100,100,100);
      break;
    default:       // default (OFF)
      for(int i=0; i<pixel.numPixels(); i++) 
      {
        pixel.setPixelColor(i,0,0,0);
        pixel.show();
        timeR = 0;
        timeG = 0;
        timeB = 0;
      }
      break;
  }
  
  /*
  
  // serial display of output colours
  int colR = (pixel.getPixelColor(0) >> 16 & 0xFF);
  int colG = (pixel.getPixelColor(0) >>  8 & 0xFF);
  int colB = (pixel.getPixelColor(0)       & 0xFF);
  if (colR>0 || colG>0 || colB>0)
  {
    Serial.print("R: ");
    Serial.print(colR);
    Serial.print(" G: ");
    Serial.print(colG);
    Serial.print(" B: ");
    Serial.println(colB);
  } */
}

// ------------------------------ FUNCTIONS ------------------------------ //


// Main function used to chase the LEDs up and down
void chase(byte valR, byte valG, byte valB)
{
  //maths to work out increment for each colour
  byte fadeR = constrain(map(timeR, 0, 100, 0, valR+12),0,100);
  byte fadeG = constrain(map(timeG, 0, 100, 0, valG+12),0,100);
  byte fadeB = constrain(map(timeB, 0, 100, 0, valB+12),0,100);
  //chase the colour on
  currenttime = millis();
  // main bit of code
    if(currenttime - previoustime > interval) // tells the function the interval at which to increment the LED brightness
    {      
      for(int i=0; i<pixel.numPixels(); i++) // incremments the LED so that you can light up multiple
      {
          if(timeR != 100, timeG != 100, timeB != 100) // tells the function to stop when time reaches 100
          {
            //        if(currenttime > (previoustime+(i*delayint)))
        {
            pixel.setPixelColor(i,curve(fadeR),curve(fadeG),curve(fadeB)); // tells the LED what value it should be at
            timeR = timeR + 1;
            timeG = timeG + 1;
            timeB = timeB + 1;
            pixel.show();
          } // close time
        } // close delay
      } // close multi LED
      previoustime = currenttime;
    } // close led brightness increment
}

// Function used when wanting to chase some LEDs but not all
void chaseArray(byte valR, byte valG, byte valB, int TheArray[])
{
  //maths to work out increment for each colour
  byte fadeR = constrain(map(timeR, 0, 100, 0, valR+12),0,100);
  byte fadeG = constrain(map(timeG, 0, 100, 0, valG+12),0,100);
  byte fadeB = constrain(map(timeB, 0, 100, 0, valB+12),0,100);
  //chase the colour on
  currenttime = millis();
  // main bit of code
    if(currenttime - previoustime > interval) // tells the function the interval at which to increment the LED brightness
    {      
      for(int i=0; i<sizeof(colorArrayR); i++) // incremments the LED so that you can light up multiple
      {
          if(timeR != 100, timeG != 100, timeB != 100) // tells the function to stop when time reaches 100
          {
            //        if(currenttime > (previoustime+(i*delayint)))
        {
            pixel.setPixelColor(TheArray[i],curve(fadeR),curve(fadeG),curve(fadeB)); // tells the LED what value it should be at
            timeR = timeR + 1;
            timeG = timeG + 1;
            timeB = timeB + 1;
            pixel.show();
          } // close time
        } // close delay
      } // close multi LED
      previoustime = currenttime;
    } // close led brightness increment
}


// LED Logarithmic Curve so the dimming looks nicer
byte curve(byte percentage)
{
// coefficients
double a = 9.7758463166360387E-01;
double b = 5.5498961535023345E-02;
// formula
return floor((a * exp(b*percentage)+.5))-1;
}


// Blinker function used for the button status lights (only works on GPIO B)
void blinker(int led, int state)
{
    if (state == 1) {      // LED ON
      mcp.digitalWrite(led, HIGH); 
    }
    else if (state == 0) { // LED OFF
      mcp.digitalWrite(led, LOW);
    }
    else {                // LED BLINK
      if((millis() % 1000) > 500) {
        mcp.digitalWrite(led, HIGH); 
      } 
      else {
        mcp.digitalWrite(led, LOW); 
      }
    }
}

First suggestion: when something doesn’t work first isolate the code which doesn’t behave like you expect.
understanding your code is made a lot more difficult by the size of your program, especially considering the amount of libraries you are using.

I believe your issue is related to the fact you are using an array to store the button states: either you use a variable and quit the loop as soon as you detect a button pressed or, in case you want to stick with the array, you need to reset the array when you identify a button press.

I personally prefer the former solution which reduces the program size on your board, but I believe you prefer to stick to your array, in which case…

  // State Counter
  for (int a=0; a<8; a++) { // for loop that looks at the whole 8 buttons
    currentState[a] = mcp.digitalRead(a); // read the immediate state of the switch
    if (currentState[a] != lastState[a]) { // checks if the previous state and the current state are different
      if (currentState[a] == HIGH) {
        for (int j=0; j<8; j++) { currentState[j] = LOW; } // resets the array
        currentState[a] = HIGH; // sets the current button as the only selected one
        counterState[a]++; // this will overflow!
      } // increments the counter
    }
    lastState[a] = currentState[a]; // sets the current state to the last state
    if (counterState[a] % 2 != 0) {blinker(15-a,2);} // if the button is evenly divisble by 2 then set the light on
    else {blinker(15-a,0);} // else set the light off
  }

Generally speaking your button management code is convoluted and unnecessarily elaborated plus it uses a counter which, for what I see, seems you use as an on/off switch… you can use a bool to achieve the same result with less space, less memory, less computation and higher readability…

I believe an example of what I would have used could help you more than just my critics, so here you go:

#define BUTTONS_SIZE
#define NO_BUTTON 255

uint8_t lastButton = NO_BUTTON;
void loop() {
  for (uint8_t i = 0; i < BUTTONS_SIZE; i++) {
    if (mcp.digitalRead(i) == HIGH) {
      // button i has been pressed
      if (lastButton == i) {
        // second time we press this button: let's disable it
        color = 'N';
      } else {
        lastButton = i;
        color = colorString[i];
      }
      break; // quit the for loop as only one button is allowed
    }
  }
  // your color switch here
}

You will need two loops. One to detect a button being pressed and the other to turn off the other buttons.

First loop will go through the buttons and whichever button was pressed first, you then remember what index it was at and use the value in the next loop.

Second loop is very simple. For this all you need to do is cycle through the button state array(assuming you are using one) and whichever index does equals your previously stored index value, you simple continue over it while setting everything else off.

In short, if it is not the same index (pressed) then turn it off

Hi rlogiacco,

Thanks so much for this code, so much more elegant!

The only thing I had to do was move the color = colorString 2 brackets later for some reason...
Works great now! Thanks!

RaraAvis:
The only thing I had to do was move the color = colorString 2 brackets later for some reason...
[/quote]
That doesn't make much sense to me... it should not even compile like that unless you replace
* *color = colorString[i];* *
with
* *color = colorString[lastButton];* *

Hi rlogiacco,

Yep I did change it to be

color = colorString[lastButton];

sorry, forgot to mention

Thanks,

C

Smaller version. This is as simple as it is going to get unless you use interrupts.

const byte Buttons[] = {2, 3, 4, 5};

#define SIZE(x) sizeof(x)/sizeof(x[0])

template<class T, size_t N>
byte RadioButtons(T(&_buttons)[N], byte State)
{
  static byte Output = 0;

  for (byte i = 0; i < N; i++)
  {
    if (digitalRead(_buttons[i]) == State)
    {
      Output = i + 1;
      break;
    }
  }
  return Output;
}

void setup() 
{
  // put your setup code here, to run once:
  Serial.begin(115200);
  for (byte i = 0; i < SIZE(Buttons); i++)
    pinMode(Buttons[i], INPUT_PULLUP);
}

void loop() 
{
  // put your main code here, to run repeatedly:

  // You can fix this part to only show the value if it has changed.
  Serial.println(RadioButtons(Buttons, LOW) );
}

Hi HazardsMind, I'm curious to know how STL is impacting the program space footprint: would you mind to compare two equivalent sketches? I'm a huge fan of OOP and STL, but I thought that would heavily impact program size... Do you use them in your projects extensively?

rlogiacco:
Hi HazardsMind, I'm curious to know how STL is impacting the program space footprint: would you mind to compare two equivalent sketches? I'm a huge fan of OOP and STL, but I thought that would heavily impact program size... Do you use them in your projects extensively?

Not extensively, no, because they can be more of a burden then helpful if you use too many. But for something like this, it doesn't hurt the memory.

Adv/Disadv of templates.

I don't have any way as of right now to test any codes, I am at work and I don't have my nano with me. But I can do it later tonight when I get the chance.

Which two sketches do you want to compare?

We might just do a simple comparison with this push button, I'm just curious about the overhead: if it's not big (well, I know big is subjective) then I might happily convert my programming style to STL and regain some flexibility I've dropped in favor of compactness.

If you think it might be helpful we can set up a Google Hangout: I'm at work as well and unable to do any test right now. I'm GMT+1 (Italy), if you are in a similar time zone it might be easier to get online together and compare the results.

Instead of using a template, I could have just done this:

char Buttons[] = {2, 3, 4, 5};
. . .

byte RadioButtons(char * _buttons, byte State)
{
  static byte Output = 0;

  for (byte i = 0; i < strlen(_buttons); i++)
  {
    if (digitalRead(_buttons[i]) == State)
    {
      Output = i + 1;
      break;
    }
  }
  return Output;
}

But it seems it is actually 50 bytes bigger than, my template version when compiled.

I removed the serial monitor parts in the following sketch.

char Buttons[] = {2, 3, 4, 5};

#define SIZE(x) sizeof(x)/sizeof(x[0])

template<class T, size_t N>
byte RadioButtons(T(&_buttons)[N], byte State) // 912 bytes
{
  static byte Output = 0;

  for (byte i = 0; i < N; i++)
  {
    if (digitalRead(_buttons[i]) == State)
    {
      Output = i + 1;
      break;
    }
  }
  return Output;
}

//byte RadioButtons(char * _buttons, byte State) // 962 bytes
//{
//  static byte Output = 0;
//
//  for (byte i = 0; i < strlen(_buttons); i++)
//  {
//    if (digitalRead(_buttons[i]) == State)
//    {
//      Output = i + 1;
//      break;
//    }
//  }
//  return Output;
//}

void setup() 
{
  // put your setup code here, to run once:
  
  for (byte i = 0; i < SIZE(Buttons); i++)
    pinMode(Buttons[i], INPUT_PULLUP);
}

void loop() 
{
  // put your main code here, to run repeatedly:
  RadioButtons(Buttons, LOW);
}