button arrays and counters

Hi, I have posted on here before. I essentially working on the same project I previously posted about, but I am rewriting the code from scratch as I seemed to be getting nowhere with it.
First off I want to start with getting multiple buttons working, which I have now successfully done thanks to the help of an online tutorial. Now what I want to do is add counters to my buttons.

To start off with, I have 3 buttons connected to digital pins 5, 6 and 7 of my arduino nano.

Now what I want to do is get them to scroll through 3 separate sets of LEDs.

So I want button 1 to scroll through LEDs 1, 2 and 3 upon each button press, (turning the previous LED off. When it reaches LED 3 and the button is pressed again, it goes back to LED 1 to start their scroll cycle again.

Button 2 to switch between LED's 4 and 5

Button 3 to scroll through LEDs 6, 7 and 8 upon each button press, however, when it reaches LED 8, and the button is pressed, LEDs 6, 7 and 8 turn off before starting their scroll cycle again.

The script I have so far only reads the buttons HIGH/LOW states and sends the serial to my computer for monitoring.

I have since connected my arduino to a TLC5940, and my LED's to the TLC5940.
I am aware that I will have to include the libraries for the TLC to the sketch in order for it to work, but before I worry about that, I want to know how to get counters to work with my sketch.
Currently I have my buttons set up as an array.
Is there a way, using my script I can add separate counters to each button in my code, or is it best to program the buttons differently?

This is my code so far:

int nbts = 3;
int startpin = 5;
int bts [3];
boolean btgs[3];

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++) bts[i] = i + startpin;
  for (int i = 0; i < nbts; i++) btgs[i] = false;
  for (int i = 0; i < nbts; i++) pinMode(bts[i], INPUT_PULLUP);



}

void loop() {
  for (int i = 0; i < nbts; i++) {
    if (!btgs[i]) {
      if (digitalRead(bts[i]) == LOW) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(1);
        btgs[i] = true;
      }
    }
    else {
      if (digitalRead(bts[i]) == HIGH) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(0);
        btgs[i] = false;
      }
    }
  }
  delay(15);

}

OK, so 3 buttons and 3 different actions that don't interact with each other. Then it's really simple to just have 3 state variables which keep track of where each action is up to. You could keep using arrays except it will get a little weird because action 0 has 3 things in its cycle and action 1 only has 2. Why not use actual names for the buttons and their actions?

Ok, so would it be easier to keep the buttons separate, and not part of an array with each of them having separate names?

If you use an array for your buttons, you can still use names.

#define BUTTON1 0
#define BUTTON2 1
#define BUTTON3 2

const byte bts[] = {5, 6, 7};
bool btgs[3] = {false, false, false};

The first part defines names; each name is an index in your array. So to read the second button, you can use e.g.

  if(digitalRead(bts[BUTTON2]) == HIGH)

The second part shows how you can initialise the arrays when you declare them; so you don't have to do that in setup().

The above is just to give you some ideas; I would stay with arrays in loops.

For your problem, you will have to keep a counter for each button; the counter is incremented each time when the button becomes activated. So this becomes a third array in your code.

Ok, thanks. I have tried to incorporate it as you have said, and now I am getting an error message when verifying the code. I'm guessing I did something wrong, but as I am not overly experienced at this, I don't know what it is.

Here is the updated code:

int nbts = 3;
int startpin = 5;
int bts [3];
boolean btgs[3];
#define BUTTON1 0
#define BUTTON2 1
#define BUTTON3 2

const byte bts[] = {5, 6, 7};
bool btgs[3] = {false, false, false};

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++) bts[i] = i + startpin;
  for (int i = 0; i < nbts; i++) btgs[i] = false;
  for (int i = 0; i < nbts; i++) pinMode(bts[i], INPUT_PULLUP);



}

void loop() {
  for (int i = 0; i < nbts; i++) {
    if (!btgs[i]) {
      if (digitalRead(bts[BUTTON1]) == LOW) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(1);
        btgs[i] = true;
      }
    }
    else {
      if (digitalRead(bts[BUTTON1]) == HIGH) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(0);
        btgs[i] = false;
      }
    }
  }
  delay(15);

}

However this is the error emssage I get:

AirCube_V5:37: error: conflicting declaration 'const byte bts []'

 const byte bts[] = {5, 6, 7};

                ^

C:\Users\Lettie\Desktop\Air3\Coding\AirCube_V5\AirCube_V5.ino:31:5: note: previous declaration as 'int bts [3]'

 int bts [3];

     ^

AirCube_V5:38: error: redefinition of 'bool btgs [3]'

 bool btgs[3] = {false, false, false};

            ^

C:\Users\Lettie\Desktop\Air3\Coding\AirCube_V5\AirCube_V5.ino:32:9: note: 'boolean btgs [3]' previously declared here

 boolean btgs[3];

         ^

exit status 1
conflicting declaration 'const byte bts []'

You have to remove the ones from your original code.

int nbts = 3;
int startpin = 5;
#define BUTTON1 0
#define BUTTON2 1
#define BUTTON3 2

const byte bts[] = {5, 6, 7};
bool btgs[3] = {false, false, false};

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++)
  {
    pinMode(bts[i], INPUT_PULLUP);
  }
}

void loop()
{
  ...
  ...
}

Note that I just gave an example how you still code use 'names'; your for-loop now does not make sense as it reads 3 time BUTTON1 and stores it in different positions in btgs.

I also should have told you that BUTTON1/2/3 are actually stupid names :wink: Usually it would be e.g. BUTTON_START, BUTTON_STOP and things like that describing the actual function.

As said, I would just stick with the original for-loop that you had for the reading of the buttons.

ok, thnk you for your help so far, the code is looking good so far, this is what I have:

int nbts = 3;
int startpin = 5;
#define BUTTON_AIRFLOW 0
#define BUTTON_HOTCOLD 1
#define BUTTON_POWERLEVEL 2

const byte bts[] = {5, 6, 7};
bool btgs[3] = {false, false, false};

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++) {
    pinMode(bts[i], INPUT_PULLUP);
  }
}

void loop() {
  for (int i = 0; i < nbts; i++) {
    if (!btgs[i]) {
      if (digitalRead(bts[BUTTON_AIRFLOW]) == LOW) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(1);
        btgs[i] = true;
      }
    }
    else {
      if (digitalRead(bts[BUTTON_AIRFLOW]) == HIGH) {
        Serial.print("bt" + String(i) + ":");
        Serial.println(0);
        btgs[i] = false;
      }
    }
  }
  delay(15);

}

I guess my next question is, is it possible to have separate counters for each button, or am I better off using state variables for the buttons?

Yes, create another array for the counters.

int nbts = 3;
int startpin = 5;
#define BUTTON_AIRFLOW 0
#define BUTTON_HOTCOLD 1
#define BUTTON_POWERLEVEL 2

const byte bts[] = {5, 6, 7};
bool btgs[] = {false, false, false};
unsigned int counters[] = {0, 0, 0};
void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++)
  {
    pinMode(bts[i], INPUT_PULLUP);
  }
}

void loop()
{
  for (int i = 0; i < nbts; i++)
  {
    if (!btgs[i]) {
      if (digitalRead(bts[i]) == LOW) {
        btgs[i] = true;
        counters[i]++;

        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(counters[i]);
      }
    }
    else {
      if (digitalRead(bts[i]) == HIGH) {
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
        btgs[i] = false;
      }
    }
  }
  delay(15);

}

I did a minor fix in the declaration of btgs; you don't have to specify the size, the compiler is clever enough. And I advise you to forget that String (capital S) exists; heavy use of it can cause memory fragmentation and as a result vague problems while your code is running.

To be continued with some more advanced stuff.

You can start considering a different approach. For each button, you currently have a pin, a state and a counter. You can combine those in a struct or class; below uses a struct.

A struct is like a record in a database; e.g. a phonebook with a first name, last name and a phone number.

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
};

I have reduced the size of the counter from 16 bits to 8 bits; saves some memory :wink:

If you ever need to expand with another variable (e.g. like adding the address in the phonebook), it's easy to add in the above and add an initial value in the below.

Next you can define an array of BUTTON

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0},
  {6, false, 0},
  {7, false, 0},
};

You can access the members of the struct using a dot as shown below

void setup()
{
  Serial.begin(9600);
  for (int i = 0; i < nbts; i++)
  {
    pinMode(buttons[i].pin, INPUT_PULLUP);
  }
}

And your loop() can now look like

void loop()
{
  for (int i = 0; i < nbts; i++)
  {
    if (!buttons[i].lastState) {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

Now we also want to get rid of nbts; everytime that you want to add or remove a button, you need to adjust it. The usual way is to use a so-called macro.

// calculate at compile time number of elements in an array
#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

sizeof(x) gives you the number of bytes of an array; sizeof(x[0]) gives you the size of an element in the array. Divide the two and you have the number of elements. This can be used on any array, regardless of type (byte, float, some struct etc). You can use the macro in the for-loops as demonstrated below (full code).

// calculate at compile time number of elements in an array
#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

// sensible button names
#define BUTTON_AIRFLOW 0
#define BUTTON_HOTCOLD 1
#define BUTTON_POWERLEVEL 2

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
};

// array of information relevant to button
BUTTON buttons[] =
{
  {5, false, 0},
  {6, false, 0},
  {7, false, 0},
};

void setup()
{
  Serial.begin(9600);
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    pinMode(buttons[i].pin, INPUT_PULLUP);
  }
}

void loop()
{
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    if (!buttons[i].lastState) {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

Note:
The for-loops now use a byte for the counter; iit's big enough for what you need and the fact that it's an unsigned variable removes some compile time warnings.

In the next step, we write functions that need to be executed when one of the buttons is pressed. You have three buttons that must do different things. So we write three functions; I have based the names on the #defines that you provided. The below is a framework.

/*
  control hot/cold
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlHotCold(byte counter)
{
  Serial.print("Hot/cold; counter modulo two: ");
  Serial.println(counter % 2);
  return true;
}

/*
  control power level
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlPowerLevel(byte counter)
{
  Serial.print("Power level; counter modulo four: ");
  Serial.println(counter % 4);
  return true;
}

Each function takes a byte as the parameter and returns a bool; you can use that to indicate that all 'steps' in a function are completed (in case something has to be done in several steps with e.g. millis() based delays). The current implementation always returns true.

The functions also show how you can count to e.g. 3 while the counter keeps on going further.
You can now call the relevant functions based on which button was pressed

void loop()
{
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    if (!buttons[i].lastState)
    {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);

        // take action
        switch (i)
        {
          case BUTTON_AIRFLOW:
            controlAirFlow(buttons[i].counter);
            break;
          case BUTTON_HOTCOLD:
            controlHotCold(buttons[i].counter);
            break;
          case BUTTON_POWERLEVEL:
            controlPowerLevel(buttons[i].counter);
            break;
        }
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

OK, that will do, but imagine you have 10 or 100 buttons; your code will grow out of proportion. There is obviously a way around that and it's called function pointers. We will add a variable to hold a function pointer to our BUTTON struct. The name of the variable is action.

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
  bool (*action)(byte counter);
};

This says that there is a function that takes a byte as parameter and will return a bool. Next you can add the relevant functions to the array.

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0, controlAirFlow},
  {6, false, 0, controlHotCold},
  {7, false, 0, controlPowerLevel},
};

This will however give you errors when you try to compile. The IDE's build process is quite intelligent but in this case not intelligent enough. It tells you that it does not know about those functions.

sketch_jun02b:28: error: 'controlAirFlow' was not declared in this scope
   {5, false, 0, controlAirFlow},
                 ^

sketch_jun02b:29: error: 'controlHotCold' was not declared in this scope
   {6, false, 0, controlHotCold},
                 ^

sketch_jun02b:30: error: 'controlPowerLevel' was not declared in this scope
   {7, false, 0, controlPowerLevel},
                 ^

exit status 1
'controlAirFlow' was not declared in this scope

You can either place those new functions before struct BUTTON or we have to tell what those functions look like; a so called prototype. If you ever develop for other systems, they might not have an IDE that tries to be intelligent and this is a requirement. I've added the protoypes before setup().

// calculate at compile time number of elements in an array
#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

// sensible button names
#define BUTTON_AIRFLOW 0
#define BUTTON_HOTCOLD 1
#define BUTTON_POWERLEVEL 2

// function prototypes
bool controlAirFlow(byte counter);
bool controlHotCold(byte counter);
bool controlPowerLevel(byte counter);

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
  bool (*action)(byte counter);
};

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0, controlAirFlow},
  {6, false, 0, controlHotCold},
  {7, false, 0, controlPowerLevel},
};

void setup()
{
  ...
  ...
}

void loop()
{
  ...
  ...
}

Now you can remove the switch/case from loop() and replace it by a call of the function specified by action.

void loop()
{
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    if (!buttons[i].lastState)
    {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);

        // take action
        buttons[i].action(buttons[i].counter);
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

Full code

// calculate at compile time number of elements in an array
#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

// function prototypes
bool controlAirFlow(byte counter);
bool controlHotCold(byte counter);
bool controlPowerLevel(byte counter);

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
  bool (*action)(byte counter);
};

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0, controlAirFlow},
  {6, false, 0, controlHotCold},
  {7, false, 0, controlPowerLevel},
};

void setup()
{
  Serial.begin(9600);
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    pinMode(buttons[i].pin, INPUT_PULLUP);
  }
}

void loop()
{
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    if (!buttons[i].lastState)
    {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);

        // take action
        buttons[i].action(buttons[i].counter);
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

/*
  control air flow
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlAirFlow(byte counter)
{
  Serial.print("Air flow; counter modulo three: ");
  Serial.println(counter % 3);
  return true;
}

/*
  control hot/cold
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlHotCold(byte counter)
{
  Serial.print("Hot/cold; counter modulo two: ");
  Serial.println(counter % 2);
  return true;
}

/*
  control power level
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlPowerLevel(byte counter)
{
  Serial.print("Power level; counter modulo four: ");
  Serial.println(counter % 4);
  return true;
}

As you can see, we now don't need the #defines for the buttons anymore.

Please understand all that I presented in this reply and the previous ones; you're the one that needs to maintain the code :wink: Ask if you don't understand ! Or stop where you do not understand something and continue in a way that you're familiar with. Function pointers are probably the most advanced stuff that you will encounter in C programming; C++ has other stuff that's (for me) far more advanced and that I don't understand and use.

holy crap thank you so much, that is incredible. Ill have to look at how to get these comands to work with a tlc5940 now, but thank you so much, this is more help than i ever expected

I have no experience with the tlc5940. But looking at some sample code like https://github.com/PaulStoffregen/Tlc5940/blob/master/examples/BasicUse/BasicUse.pde, it should be reasonably easy.

In each of the three functions, set the value for the relevant channels (not all channels) based on the counter value and call the update method.

Exact implementation might depend on the library that you have chosen.

Once again thank you, I have taken a quick look at his libraries, but I'll have a proper look when I have free time. Your help has been extraordinary though. More helpful than I have ever received in the past. You really know how to make a girl happy

Oh and btw, I meant that in a strictly plutonic way lol I don't want to cause confusion

Based on what I've seen online, basic coding for the TLC5940 to set functions of the pins is like this:

Tlc.set (22, 2730);
Tlc.set (4, 2354);
Tlc.set (5, 0);
Tlc.update();

This is usually in the void loop section of the sketch from what I've seen. From what I can tell, this is for two TLC 5940s that are daisy chained, allowing the output pins to continue.
Eg, tlc (1) outputs pins 0-15, tlc (2) output pins 16-31 and so on.

So in this example; Tlc.set (5, 0); sets tlc output pin 5 pwm signal to 0, whereas Tlc.set (22, 2730); sets tlc output pin 22 pwm signal to 2730.
The maximum pwm the TLC can output is 4095.

Because what I want to do with the button presses in order to control certain components, is there a way to incorporate this into the current version of the code?

For example, I have RGB LEDs connected to TLC output pins 0 - 15 of my first TLC unit, and motors connected to h- bridge units which are wired to the second TLC.
So let's say I want the airflow button to control LEDs 2 (tlc out pins 2 & 3), 3 (tlc out pins 4 & 5), 4 (tlc out pins 6 & 7) and a motor speed connected to TLC out pin 16 via a h-bridge.
This would be the Airflow function that I require the Airflow button to perform.

Would I need to write this out as some sort of array for this to work using my current sketch? Or is there a better way to do it?

Because what I want to do with the button presses in order to control certain components, is there a way to incorporate this into the current version of the code?

Yes, you can place what is usually presented in the loop() function, in one of the three functions for which I gave you the framework,

Would I need to write this out as some sort of array for this to work using my current sketch? Or is there a better way to do it?

You can write it out completely, you can use arrays or you can use structs.

If we continue with structs, you can start by combining a PWM value and a pin number in a struct.

// information for a TLC pin/value pair
struct TLCPINVALUE
{
  byte pin;
  int value;
};

And next you can define three structs that make use of the above; demonstrated for airflow

// airflow control information
struct AIRFLOWCONTROL
{
  TLCPINVALUE leds[6];
  TLCPINVALUE motor;
};

According to your description, you have 6 leds (3x2) that you want to control and one motor. You can create similar structs HOTCOLDCONTROL and POWERLEVELCONTROL; they can use the same TLCPINVALUE struct.

Next you can adjust the controlAirFlow function. First add an array of AIRFLOWCONTROL structures, one for each count.

bool controlAirFlow(byte counter)
{
  AIRFLOWCONTROL ctrl[] =
  {
    // counter == 0
    {
      // array of TLC for all leds related to airflow
      {
        // 1       2       3       4       5      6
        {2, 2048}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},
      },
      // single TLC for airflow motor
      {16, 0}
    },

    // counter == 1
    {
      {
        {2, 0}, {3, 0}, {4, 2048}, {5, 0}, {6, 0}, {7, 0},
      },
      {16, 512}
    },

    // counter == 2
    {
      // L1      L2      L3      L4      L5         L6         M
      {{2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 2048}, {7, 0},}, {16, 2048}
    },
  };
  ...
  ...

For clarity (or confusion), I have shown three ways that are the same, just spread over more lines or less lines. The first one is the easiest to follow with documentation, the second one is a bit shorter (less documentation) and the last one is what I usually use because it's shorter (see full code at end).

Next we can modify the print of the module to take into account the number of elements in the ctrl array.

 Serial.print("Air flow; counter modulo X: ");
  Serial.println(counter % NUMELEMENTS(ctrl));

The above is purely for debugging and can later be removed; the print result should be 0, 1, 2, else something is wrong.

In the last step you can control the leds and the motor. The variable counter that is passed to the function is now an indicator which indicates which element of the ctrl array needs to be accessed. To limit the index we can use a modulo to access the correct element in the array.

ctrl[counter % NUMELEMENTS(ctrl)]

This way there is never a risk that you try to access an element outside the ctrl array and it's easy to add an element to the ctrl array without having to adjust the rest of the code.

For the leds, we we loop through the led array that is part of the ctrl that is indicated by counter. For the motor, we don't need a loop.

 // set the leds
  for (byte ledCnt = 0; ledCnt < NUMELEMENTS(ctrl[counter % NUMELEMENTS(ctrl)].leds); ledCnt++)
  {
    Serial.print("Led# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].pin);
    Serial.print(", PWM "); Serial.println(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].value);
  }

  // set the motor
  Serial.print("Motor# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.pin);
  Serial.print(", PWM "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.value);

You can replace the serial prints by the actual controlling of the leds and the motor. The serial prints are useful for the debugging.

Full code for controlAirFlow

// information for a TLC pin/value pair
struct TLCPINVALUE
{
  byte pin;
  int value;
};

// airflow control information
struct AIRFLOWCONTROL
{
  TLCPINVALUE leds[6];
  TLCPINVALUE motor;
};

/*
  control air flow
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlAirFlow(byte counter)
{
  AIRFLOWCONTROL ctrl[] =
  {
    {{{2, 2048}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 0}},    // leds 1..6, motor
    {{{2, 0}, {3, 0}, {4, 2048}, {5, 0}, {6, 0}, {7, 0},}, {16, 512}},
    {{{2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 2048}, {7, 0},}, {16, 2048}},
  };

  Serial.print("Air flow; counter modulo X: ");
  Serial.println(counter % NUMELEMENTS(ctrl));

  // set the leds
  for (byte ledCnt = 0; ledCnt < NUMELEMENTS(ctrl[counter % NUMELEMENTS(ctrl)].leds); ledCnt++)
  {
    Serial.print("Led# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].pin);
    Serial.print(", PWM "); Serial.println(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].value);
  }

  // set the motor
  Serial.print("Motor# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.pin);
  Serial.print(", PWM "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.value);

  return true;
}

One improvement can be made if necessary; you might not want to control the leds and motor again if the counter did not change.

I have not been able to test this but it does compile.

PS
Because the ctrl array is local to the function, you can name the arrays in the other two functions ctrl as well and there will be no conflict. If ctrl was global (basically defined before setup), you would have to give them different names.

With the LEDs I think I may have caused confusion. I am using a total of 8 RGB LEDs. LED 1 is to indicate power on
2 -4 is airflow
5 is hot and cold
6 - 8 are for power level
As I only need 2 colours from each of the LEDs to achieve the desired colour, for all LEDs except hot and cold LED, which although has 2 colours connected, it's just swapping between the 2 colours ( blue and red).
So LED 1 for example would have a pwm signal of the blue pin (TLC out 0) would be set to 2354, while the green pin (TLC out 1) is set to 1740 at the same time as the blue pin.
Is this going to have a major affect on how the struct is written?

Also this may seem like a stupid question, but when I turn the device on, I want the power indicator LED (led 1) to turn on automatically, as well as automatically set airflow to its first setting so the motor is running on low with its low LED (LED 2) to automatically turn on. Would I write this information in at void setup or void loop?

SeeTheWinter:
With the LEDs I think I may have caused confusion. I am using a total of 8 RGB LEDs. LED 1 is to indicate power on
2 -4 is airflow
5 is hot and cold
6 - 8 are for power level
As I only need 2 colours from each of the LEDs to achieve the desired colour, for all LEDs except hot and cold LED, which although has 2 colours connected, it's just swapping between the 2 colours ( blue and red).
So LED 1 for example would have a pwm signal of the blue pin (TLC out 0) would be set to 2354, while the green pin (TLC out 1) is set to 1740 at the same time as the blue pin.
Is this going to have a major affect on how the struct is written?

The code that I presented was written with the intent to support that. In de example, I only gave one pin a non-zero value in each of the ctrl elements. But you can easily change that if the colour of a LED has to be a mix of two colours.

  AIRFLOWCONTROL ctrl[] =
  {
    {{{2, 2048}, {3, 0}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 0}},    // leds 1..6, motor
                     ^
                     |
    ...

  AIRFLOWCONTROL ctrl[] =
  {
    {{{2, 2354}, {3, 1740}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 0}},    // leds 1..6, motor
                     ^
                     |
    ...

SeeTheWinter:
Also this may seem like a stupid question, but when I turn the device on, I want the power indicator LED (led 1) to turn on automatically, as well as automatically set airflow to its first setting so the motor is running on low with its low LED (LED 2) to automatically turn on. Would I write this information in at void setup or void loop?

Initialiation of the system is a once-off process en hence you place it in setup(). You can place it in loop() but in that case you have to keep track that it was done which gives some overhead.
In setup(), you can call the three functions. Remember this part?

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0, controlAirFlow},
  {6, false, 0, controlHotCold},
  {7, false, 0, controlPowerLevel},
};

It initialises the array of buttons and one of the elements was the the counter for the button that is initialised with 0. So if you call the functions in setup(), it will use the first array element (index 0). For the airflow control, that means tlc pin 2 set to 2354 and tlc pin 3 set tp 1740 and the motor (on pin 16) will be off (PWM 0).

To give the motor a speed when counter equals 0, change

  AIRFLOWCONTROL ctrl[] =
  {
    {{{2, 2354}, {3, 1740}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 0}},    // leds 1..6, motor off
                                                                   ^
                                                                   |
    ...

  AIRFLOWCONTROL ctrl[] =
  {
    {{{2, 2354}, {3, 1740}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 256}},    // leds 1..6, motor at slow speed
                                                                   ^
                                                                   |
    ...

Ok, so based on what you have said, and the information I have at hand, this is how I have writen the code:

// Libraries needed for TLC5940
#include "Tlc5940.h"
#include "tlc_config.h"
#include "pin_functions.h"

// calculate at compile time number of elements in an array
#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])

// function prototypes
bool controlAirFlow(byte counter);
bool controlHotCold(byte counter);
bool controlPowerLevel(byte counter);

// button related information grouped together
struct BUTTON
{
  const byte pin;
  bool lastState;
  byte counter;
  bool (*action)(byte counter);
};

// array of information relevant to buttons
BUTTON buttons[] =
{
  {5, false, 0, controlAirFlow},
  {6, false, 0, controlHotCold},
  {7, false, 0, controlPowerLevel},
};

void setup()
{
  Serial.begin(9600);
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    pinMode(buttons[i].pin, INPUT_PULLUP);
  }
}

void loop()
{
  for (byte i = 0; i < NUMELEMENTS(buttons); i++)
  {
    if (!buttons[i].lastState)
    {
      if (digitalRead(buttons[i].pin) == LOW)
      {
        buttons[i].lastState = true;
        buttons[i].counter++;
        Serial.print("bt"); Serial.print(i);
        Serial.print(": 1 -> ");
        Serial.println(buttons[i].counter);

        // take action
        buttons[i].action(buttons[i].counter);
      }
    }
    else
    {
      if (digitalRead(buttons[i].pin) == HIGH)
      {
        buttons[i].lastState = false;
        Serial.print("bt"); Serial.print(i);
        Serial.println(": 0");
      }
    }
  }
  delay(15);
}

/*
  control air flow
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlAirFlow(byte counter)
{
  // information for a TLC pin/value pair
  struct TLCPINVALUE
  {
    byte pin;
    int value;
  };

  // airflow control information
  struct AIRFLOWCONTROL
  {
    TLCPINVALUE leds[6];
    TLCPINVALUE motor;
  };

  /*
    control air flow
    In:
      counter
    Returns:
      true when finished, else false
  */
  bool controlAirFlow(byte counter)
  {
    AIRFLOWCONTROL ctrl[] =
    {
      {{{2, 2354}, {3, 1740}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 1365}, {19, 1365}, {21, 1365}},   // RGBleds 2..4, motor
      {{{2, 0}, {3, 0}, {4, 2354}, {5, 1740}, {6, 0}, {7, 0},}, {16, 2730}, {19, 2730}, {21, 2730}},
      {{{2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 2354}, {7, 1740},}, {16, 1365}, {19, 1365}, {21, 1365}},
    };

    Serial.print("Air flow; counter modulo X: ");
    Serial.println(counter % NUMELEMENTS(ctrl));

    // set the leds
    for (byte ledCnt = 0; ledCnt < NUMELEMENTS(ctrl[counter % NUMELEMENTS(ctrl)].leds); ledCnt++)
    {
      Serial.print("Led# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].pin);
      Serial.print(", PWM "); Serial.println(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].value);
    }

    // set the motor
    Serial.print("Motor# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.pin);
    Serial.print(", PWM "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].motor.value);

    return true;
  }
}

/*
  control hot/cold
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlHotCold(byte counter)
  {
    AIRFLOWCONTROL ctrl[] =
    {
      {{{8, 4095}, {9, 0}, }},   // RGBleds 5
      {{{8, 0}, {9, 4095}, }},

    };

    Serial.print("Air flow; counter modulo X: ");
    Serial.println(counter % NUMELEMENTS(ctrl));

    // set the leds
    for (byte ledCnt = 0; ledCnt < NUMELEMENTS(ctrl[counter % NUMELEMENTS(ctrl)].leds); ledCnt++)
    {
      Serial.print("Led# "); Serial.print(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].pin);
      Serial.print(", PWM "); Serial.println(ctrl[counter % NUMELEMENTS(ctrl)].leds[ledCnt].value);
    }
    return true;
  }

/*
  control power level
  In:
    counter
  Returns:
    true when finished, else false
*/
bool controlPowerLevel(byte counter)
{
  Serial.print("Power level; counter modulo four: ");
  Serial.println(counter % 4);
  return true;
}

I didn't complete the power level code and left it as a serial print for the time being as what I need it to do will be more complicated, I'll explain later.

However, I am now getting this error message:

Arduino: 1.8.5 (Windows 10), Board: "Arduino Nano, ATmega328P"

In file included from sketch\Tlc5940.h:26:0,

                 from C:\Users\Lettie\Desktop\Air3\Coding\AirCube_V5\AirCube_V5.ino:29:

sketch\tlc_config.h:65:27: fatal error: chip_includes.h: No such file or directory

 #include "chip_includes.h"

                           ^

compilation terminated.

exit status 1
Error compiling for board Arduino Nano.

This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.

I'm tipping that I stuffed something up somewhere, or deleted the wrong thing. I just cant work out what I did wrong though. I don't even know if I wrote the updated information in the right section

1)
Which library are you using? I suspect that it's not the one from Paul Stoffregen; that is not a problem but I need to know so I can install the same one. And how did you install it?

===

2)
You have at least one bug in the code, possibly due to a copy/paste error.

If you properly indent (tools -> autoformat in the IDE), you will basically see

bool controlAirFlow(byte counter)
{
  ...
  ...

  bool controlAirFlow(byte counter) <<<<============
  {

The fact that a function definition does not start at the beginning of a line indicates that you're somewhere missing a }. In this case however I suspect that you copied something to the wrong place.

====

3a)
You also don't seem to understand how it is supposed to work or which way I wanted to go.

The AIRFLOWCONTROL struct is specific for the controlling of the airflow; for hot/cold, you need to create a new struct.

// information for a TLC pin/value pair
struct TLCPINVALUE
{
  byte pin;
  int value;
};

// airflow control information
struct AIRFLOWCONTROL
{
  TLCPINVALUE leds[6];
  TLCPINVALUE motor;
};

add the below for the hot/cold control
// hot cold control information
struct HOTCOLDCONTROL
{
  TLCPINVALUE leds[6];  <<<=====
  TLCPINVALUE motor;
};

Change the 6 to the number of leds that relate to the hot/cold control; assuming that hot/cold has 2 leds and hence 4 pins, it should be 4.

3b)
Next you have added extra elements to the leds/motor in the ctrl[] array

    AIRFLOWCONTROL ctrl[] =
    {
      {{{2, 2354}, {3, 1740}, {4, 0}, {5, 0}, {6, 0}, {7, 0},}, {16, 1365}, {19, 1365}, {21, 1365}},   // RGBleds 2..4, motor
      {{{2, 0}, {3, 0}, {4, 2354}, {5, 1740}, {6, 0}, {7, 0},}, {16, 2730}, {19, 2730}, {21, 2730}},
      {{{2, 0}, {3, 0}, {4, 0}, {5, 0}, {6, 2354}, {7, 1740},}, {16, 1365}, {19, 1365}, {21, 1365}},
    };

You can't do that without modifying the definition of the struct. In your description there was one motor pin; what are your additional pins for? The other motors? If they are for the other motors, see above where you define a new struct HOTCOLDCONTROL which will also contain the motor pin.

Think of it this way
If you had a phonebook with last name (the led pins and values) and a phone number (the motor pin and values), why would you add the phone number of your neighbours to that same entry in the phone book?

With 3a in place, the controlHotCold would start with this (assuming 4 led pins)

bool controlHotCold(byte counter)
{
  // for demo purposes, hot/cold control has two levels, low and high
  HOTCOLDCONTROL ctrl[] =
  {
    // 2 rgb leds (pins 7/8 and pins 9/10) and one motor pin (19)
    {{{7, 2354}, {8, 1740}, {9, 0}, {10, 0},}, {19, 1365}},   // low level
    {{{7, 0}, {8, 0}, {9, 2354}, {10, 1740},}, {19, 4000}},   // high level
  };

  ...
  ...
}

I think that that is enough for now :wink: