Individually addressable LED strip with various recipes

I have a large strip of individually addressable LEDs and an Arduino Mega2560. The LED lights are visual locators for components in an assembly process, and the color represents the orientation of the component.

I have 30-45 variations of a sketch that I would like the operator to be able to select from, and the lights and colors change according to the selected sketch. Variables include the number of LEDs to illuminate on the strip, the ID location of each LED to illuminate, and the color to illuminate. I DO NOT want the operators to have access to the sketch but need to have them select the recipe and then the LEDs to update accordingly.

A co-worker of mine came up with a solution utilizing VB to send a concatenated string via serial to the controller that then deconstructs the string and updates the variables.

Is there a more simplistic way to just have 30-45 named sketches and have someone select the sketch and upload it without giving them the access to modify the code?

Why do you think you need 30-45 sketches? That does not sound like a sensible solution.

It sounds to me like you need one sketch that can send one of 30-45 patterns to the led strip, yes?

Yes if I could figure out how to do that with a pattern. The configurations do not follow any numerical pattern that I could use to meet my configurations. the 30-45 is the number of recipes (or parts) I need for production. I could name each sketch the part ID it is assigned by my organization so the operator picks the part and is in turn selecting the corresponding sketch. Up to this point I either need 30-45 sketches to choose between, or 30-45 part configurations to choose between in VB that is then sent to a single sketch discretely programmed to modify all the variables to the VB configuration.

How many LEDs in your "large" strip?

Why do you want to use VB?

Have you got some of these sketches working? Can you post a couple? Please read the forum sticky post to find out how to post sketches properly.

I will have a total of 1,500 LEDs in the strip when all is said and done.

I don't want to use VB if I don't have to, but it was one option for a solution that a co-worker proposed. I am trying to avoid it to simplify the solution to a single program language if possible so that it is easier to maintain as products go through their life cycles.

I will review the sticky post and post some sketches that represent what I am trying to do ASAP.

So, the operator selects one of 45 recipies. How will that be done? The Arduino then lights up to 1500 LEDs indicating which of the components are required and different colours indicate different orientations of those components. How many colours/orientations? How is the sequence of components indicated to the operator so they can construct the product?

The patterns need to alterable as the recipies evolve over time. How often will the patterns need to be updated? Would it be acceptable to upload an updated sketch when this needs doing? Another, but more complex, solution might be to hold the patterns on a sd card.

One configuration:

#include "FastLED.h"
#define NUM_LEDS 1500
#define DATA_PIN 3

CRGB leds[NUM_LEDS];

void setup() { 
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop() { 
//leds[ID] = CRGB(red, green, blue) Only use value of 10 to reduce the amp draw of the strip when lit
  leds[0] = CRGB(10,0,0);
  leds[10] = CRGB(10,0,0);
  leds[165] = CRGB(0,0,10);
  leds[298] = CRGB(0,0,10);
  leds[360] = CRGB(10,0,0);   
  leds[514] = CRGB(0,0,10);
  leds[759] = CRGB(10,0,0);
  leds[1200] = CRGB(10,0,0);
       
  FastLED.show();
}

Another configuration:

#include "FastLED.h"
#define NUM_LEDS 1500
#define DATA_PIN 3

CRGB leds[NUM_LEDS];

void setup() { 
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}

void loop() { 
//leds[ID] = CRGB(red, green, blue) Only use value of 10 to reduce the amp draw of the strip when lit
  leds[37] = CRGB(10,0,0);
  leds[119] = CRGB(10,0,0);
  leds[254] = CRGB(0,0,10);
  leds[357] = CRGB(10,0,0);
  leds[419] = CRGB(0,0,10);
  leds[584] = CRGB(10,0,0);
  leds[632] = CRGB(0,0,10);
  leds[707] = CRGB(10,0,0);
  leds[837] = CRGB(10,0,0);
  leds[994] = CRGB(0,0,10);
  leds[1080] = CRGB(10,0,0);
  leds[1121] = CRGB(10,0,0);   
  leds[1293] = CRGB(0,0,10);
  leds[1372] = CRGB(10,0,0);
  leds[1416] = CRGB(10,0,0);
       
  FastLED.show();
}

Since I am new to this board please let me know if I this is posted correctly or not.

To be as clear as possible I copied your text and responded in bold.

So, the operator selects one of 45 recipies. How will that be done? This is one of the items I am trying to figure out. There will be a PC and controller at their work station which is 30 feet in length. They will select the recipe to run and the controller modifies the LEDs that are lit to the recipe with the correct color of light. This is also where my co-worker suggested VB because a form could be used to select from a dropdown of the various recipes.

The Arduino then lights up to 1500 LEDs indicating which of the components are required and different colours indicate different orientations of those components. How many colours/orientations? The arduino is only going to be lighting up to 50 of the 1,500 LEDs available at a time to one of only 2 selected colorsHow is the sequence of components indicated to the operator so they can construct the product?The operator is building on top of a transparent surface in front of the lights. The lights are in the location where their sub-components go, so they are covering the lights with their sub-components as they are building their assembly.

The patterns need to alterable as the recipies evolve over time. How often will the patterns need to be updated? Patterns may be modified annually, and some new patterns are added over time.Would it be acceptable to upload an updated sketch when this needs doing? Another, but more complex, solution might be to hold the patterns on a sd card.The PC at the location will have enough memory on it to hold the various patterns.

It sounds like the Arduino’s role here is simply an interface between an app on the PC and the led strip. The PC app has the user interface and stores the recipies. It then sends the required pattern to the Arduino which displays it on the strip.

There is a danger here of over complicating the project, from your point of view, and re-inventing the wheel. There must be products out there that are designed for this use case. Using one of those products would save you time and reduce the complexity of the project for you. One less language involved and no Arduino to program.

Maybe this?

But if you want to use an Arduino, for your own education or other reason, it should be perfectly possible. The main issue I can see is that the Arduino needs nothing more than a usb serial interface and a single pin to drive the strip. So the smallest Arduino will have enough pins and speed. However, most small Arduino have very limited RAM memory, e.g. 2K. But for 1500 LEDs, it will need at least 8K, I suspect. Mega has this much, but has a large number of pins which would be a waste.

I came across this Trinket M0 recently which might fit the bill perfectly.

Those two recipes you showed are all static, that is once set they don’t change. Do you have moving recipes as well?

PaulRB - you hit the nail on the head. I thought the VB solution was too complicated which is why I reached out to this board to see if anyone had any other ideas. There has to be a more simple solution out there already. I will look into the FadeCandy and give it a shot. I am not familiar with python, but it does not look difficult for my specific application.

Also, even though there are 1,500 LEDs, only 110 will be used with the various configurations for the first phase of this project. The reason I selected a 1,500 LED strip was because of the light spacing being close to 1/4" which is what I need. The reason I selected the Mega was because of the other input pins. My thoughts for phase 2 was to have IR sensors fed back to the Mega in specific spots to verify whether a part was actually placed in the right location. This would require multiple Megas though to get to the 110 inputs.

Grumpy_Mike - All recipes light location values are static and don't change once upladed. They change from recipe to recipe, but not within a single recipe. They are building a unit and fixing parts together. Kind of like a stud wall in construction, but instead of a 2x4 being placed every 16" apart, the components are placed a distance of the location of each light. Their "stud" covers the light and then they fix it all together. Once its assembled, they remove the wall, select the next recipe which changes the lights configuration, and start building the next wall.

That means the data you store can be restricted to just the numbers of lights that are on and you don’t have to store the state of all the lights. That will cut down a lot on the data you have to store.

Grumpy_Mike - exactly. I just reviewed the various products and there are 46 variations. However, I will only have 9 lights on at a time while they build their unit. That's one of the things that is so frustrating. My code would only be 9 lines long if I created a sketch for each part which is completely do-able. Its just the interface for production that I want to secure. I need to figure out a way of having them select the pattern (whether it is a sketch or configuration) and upload it without being able to see the code or modify it.

But you don’t need to upload any code, just upload the data ( the 9 LED numbers ) that you want to light and turn all the others off.
In fact is is simpler to turn off all the LEDs and then turn on the ones you want.

I can change the LED numbers and colors but need to be able to create a way for the operators to be able to do it by selecting a pre-determined recipe. How could they do that without access to the code so that when they select the recipe, it automatically updates and changes?

As I explained back in post #2, you need to forget this strange obsession that you need multiple sketches and have to be continually updating them every time you want a different recipe displayed. It's a bad idea, for the reasons you already mentioned, plus the Arduino's flash memory would eventually wear out.

I think I understand where your idea comes from. You have represented the recipe pattern in the form of static program code. So you think the only way to change the pattern is to change the code. But the pattern can also be represented as data, which a single program can send to the strip no matter what pattern the data represents. This data could be stored in the Arduino's flash memory, or transmitted via the usb cable from a second program on the PC, or stored on an SD card. This is a basic principle of computers. The Jacquard looms used the same principle with the patterns recorded on cards as a series of holes. Before that, a single loom could only produce the single pattern it was designed to produce.

Mike, you mentioned that the Arduino would not need enough ram for 1500 LEDs if only a few hundred were being used. Can you explain how this would be achieved? Using either of the two mainstream libraries for ws2812b pixels, I think you would still need the ram for all 1500, even if 90% of them were off. Is your idea to use some lower level library, where the off pixels do not need to be stored in ram?

timfran87: I can change the LED numbers and colors but need to be able to create a way for the operators to be able to do it by selecting a pre-determined recipe. How could they do that without access to the code so that when they select the recipe, it automatically updates and changes?

Let's assume that you can store all patterns in your Arduino in one way or another. You can use the serial port to read a number from a terminal program (e.g. serial monitor or something that supports VT codes) that reflects the pattern to be shown.

You currently have 46 patterns with 9 leds; that's 46 * 9 * 3 = 1242 bytes; add an integer for indication of the led and you will need 2070 bytes. Those you can store in progmem if they never change or in eeprom if you feel that the recipes might ever change (needs a big enough Arduino).

Once you have the number, the code can read the corresponding pattern and place it on the ledstrip.

you mentioned that the Arduino would not need enough ram for 1500 LEDs if only a few hundred were being used. Can you explain how this would be achieved?

No I think you misunderstood, it was the fact you don't need to store the data for each LED for each recipe. You still need the LED buffer somewhere.

but need to be able to create a way for the operators to be able to do it by selecting a pre-determined recipe. How could they do that without access to the code so that when they select the recipe, it automatically updates and changes?

Yes you can do that at the computer end. The operator selects a recipe number, that in turn looks up what LEDs need turning on from an array. The the program sends those LED numbers to the Arduino. In other words the whole thing is "data driven" not method driven.

sterretje: You currently have 46 patterns with 9 leds; that's 46 * 9 * 3 = 1242 bytes; add an integer for indication of the led and you will need 2070 bytes. Those you can store in progmem if they never change or in eeprom if you feel that the recipes might ever change (needs a big enough Arduino).

timfran87: Also, even though there are 1,500 LEDs, only 110 will be used with the various configurations for the first phase of this project.

OOPS, I just see that it's not a maximum of 9 leds per pattern but a maximum of 110 leds per pattern; not sure where I got the 9 from. That's 25300 bytes total. For a progmem solution, that will probably still fit in an Uno (so you're OK with a Mega); for an eeprom solution, that will mean an external eeprom.

// Edit Hmm, just compiled one of your sketches for an Uno and it does not fit (FastLed consumes quite a bit of memory as well).

For each LED, we can use a so-called struct that combines the information for a single led.

struct LEDINFO
{
  int num;
  byte red;
  byte green;
  byte blue;
};

And we can combine all those in a two-dimensional array. Below is based on the two patterns that you gave; I hope that I did not make copy/paste/edit mistakes. Note that the led numbers are one-based (starting from 1); reason is that leds that are not populated in the below array are automatically set to 0 (number, red, green and blue).

LEDINFO patterns[][110] =
{
  // pattern 01
  {
    {38, 10, 0, 0},
    {120, 10, 0, 0},
    {255, 0, 0, 10},
    {358, 10, 0, 0},
    {420, 0, 0, 10},
    {585, 10, 0, 0},
    {633, 0, 0, 10},
    {708, 10, 0, 0},
    {838, 10, 0, 0},
    {995, 0, 0, 10},
    {1081, 10, 0, 0},
    {1122, 10, 0, 0},
    {1294, 0, 0, 10},
    {1375, 10, 0, 0},
    {1417, 10, 0, 0},
  },
  // pattern 02
  {
    { 1, 10, 0, 0},
    { 11, 10, 0, 0},
    { 166, 0, 0, 10},
    { 299, 0, 0, 10},
    { 361, 10, 0, 0},
    { 515, 0, 0, 10},
    { 760, 10, 0, 0},
    { 1201, 10, 0, 0},
  },
};

Below setup demonstrates how to use it. It loops through the patterns and for each pattern it loops through all leds. It skips leds with number 0 so the serial monitor is not flooded.

void setup()
{
  Serial.begin(57600);

  for (uint16_t patCnt = 0; patCnt < sizeof(patterns) / sizeof(patterns[0]); patCnt++)
  {
    Serial.print("Pattern "); Serial.println(patCnt + 1);
    for (uint16_t ledCnt = 0; ledCnt < sizeof(patterns[0]) / sizeof(patterns[0][0]); ledCnt++)
    {
      if (patterns[patCnt][ledCnt].num != 0)
      {
        Serial.print("\tLed "); Serial.print(patterns[patCnt][ledCnt].num);
        Serial.print("\tr = "); Serial.print(patterns[patCnt][ledCnt].red);
        Serial.print("\tg = "); Serial.print(patterns[patCnt][ledCnt].green);
        Serial.print("\tb = "); Serial.print(patterns[patCnt][ledCnt].blue);
        Serial.println();
      }
    }
  }
}

void loop()
{
}

The construction with sizeof is used to (at compile time) determine how many patterns there are and how many leds there are in a pattern. Usually one defines a macro for that that can take any type of array (int, float, struct) and will calculate the number of elements.

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

After compiling the above code

Sketch uses 3060 bytes (9%) of program storage space. Maximum is 32256 bytes.
Global variables use 1320 bytes (64%) of dynamic memory, leaving 728 bytes for local variables. Maximum is 2048 bytes.

This will take 1320 bytes of RAM; I’m basically at the limit of my Uno.

So let’s move your patterns to PROGMEM

const LED patterns[][110] PROGMEM =
{
  ...
  ...
};
Sketch uses 3060 bytes (9%) of program storage space. Maximum is 32256 bytes.
Global variables use 220 bytes (10%) of dynamic memory, leaving 1828 bytes for local variables. Maximum is 2048 bytes.

OK, 1100 bytes saved. Do not run it as you will have garbage patterns.

You now need to read from PROGMEM. The below reads the information for the leds (one at a time) for each pattern

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

struct LEDINFO
{
  int num;
  byte red;
  byte green;
  byte blue;
};

const LEDINFO patterns[][110] PROGMEM =
{
  // pattern 01
  {
    {38, 10, 0, 0},
    {120, 10, 0, 0},
    {255, 0, 0, 10},
    {358, 10, 0, 0},
    {420, 0, 0, 10},
    {585, 10, 0, 0},
    {633, 0, 0, 10},
    {708, 10, 0, 0},
    {838, 10, 0, 0},
    {995, 0, 0, 10},
    {1081, 10, 0, 0},
    {1122, 10, 0, 0},
    {1294, 0, 0, 10},
    {1375, 10, 0, 0},
    {1417, 10, 0, 0},
  },
  // pattern 02
  {
    { 1, 10, 0, 0},
    { 11, 10, 0, 0},
    { 166, 0, 0, 10},
    { 299, 0, 0, 10},
    { 361, 10, 0, 0},
    { 515, 0, 0, 10},
    { 760, 10, 0, 0},
    { 1201, 10, 0, 0},
  },
};

void setup()
{
  Serial.begin(57600);

  for (uint16_t patCnt = 0; patCnt < NUMELEMENTS(patterns); patCnt++)
  {
    Serial.print("Pattern "); Serial.println(patCnt + 1);
    for (uint16_t ledCnt = 0; ledCnt < NUMELEMENTS(patterns[0]); ledCnt++)
    {
      // a work copy for a led
      LEDINFO led;
      // retrieve from PROGMEM
      memcpy_P(&led, &patterns[patCnt][ledCnt], sizeof(LEDINFO));

      // if the number is not zero, it's an 'active' led
      if (led.num != 0)
      {
        Serial.print("\tLed "); Serial.print(led.num);
        Serial.print("\tr = "); Serial.print(led.red);
        Serial.print("\tg = "); Serial.print(led.green);
        Serial.print("\tb = "); Serial.print(led.blue);
        Serial.println();
      }
    }
  }
}

void loop()
{
}

Instead of serial printing, you can use the led info to set the leds. First create two functions, one to clear the leds and one to set the leds.

/*
  clear all leds of the strip
*/
void clearLeds()
{
  // loop through all leds and clear them
  for (uint16_t ledCnt = 0; ledCnt < NUM_LEDS; ledCnt++)
  {
    leds[ledCnt] = CRGB(0, 0, 0);
  }

  // show the result
  FastLED.show();
}

/*
  set the leds for a specified pattern
  In:
    zero based pattern number
*/
void setLeds(int patternNumber)
{
  Serial.print("Pattern "); Serial.println(patternNumber + 1);

  // loop through all leds for the given pattern
  for (uint16_t ledCnt = 0; ledCnt < NUMELEMENTS(patterns[patternNumber]); ledCnt++)
  {
    // a work copy for a led
    LEDINFO led;
    // retrieve from PROGMEM
    memcpy_P(&led, &patterns[patternNumber][ledCnt], sizeof(LEDINFO));

    // if the number is not zero, it's an 'active' led
    if (led.num != 0)
    {
      // led numbers are one-based, subtract 1 from num
      leds[led.num - 1] = CRGB(led.red, led.green, led.blue);
    }
  }
  // show the result
  FastLED.show();
}

The full demo code that will display the two patterns for 5 seconds

#include <FastLED.h>
#define NUM_LEDS 1500
#define DATA_PIN 3

#define NUMELEMENTS(x) sizeof(x)/sizeof(x[0])
#define MAX_LEDS 110

CRGB leds[NUM_LEDS];

struct LEDINFO
{
  int num;
  byte red;
  byte green;
  byte blue;
};

const LEDINFO patterns[][MAX_LEDS] PROGMEM =
{
  // pattern 01
  {
    {38, 10, 0, 0},
    {120, 10, 0, 0},
    {255, 0, 0, 10},
    {358, 10, 0, 0},
    {420, 0, 0, 10},
    {585, 10, 0, 0},
    {633, 0, 0, 10},
    {708, 10, 0, 0},
    {838, 10, 0, 0},
    {995, 0, 0, 10},
    {1081, 10, 0, 0},
    {1122, 10, 0, 0},
    {1294, 0, 0, 10},
    {1375, 10, 0, 0},
    {1417, 10, 0, 0},
  },
  // pattern 02
  {
    { 1, 10, 0, 0},
    { 11, 10, 0, 0},
    { 166, 0, 0, 10},
    { 299, 0, 0, 10},
    { 361, 10, 0, 0},
    { 515, 0, 0, 10},
    { 760, 10, 0, 0},
    { 1201, 10, 0, 0},
  },
};

void setup()
{
  Serial.begin(57600);

  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);

  clearLeds();
  setLeds(0);
  delay(5000);
  clearLeds();
  setLeds(1);
  delay(5000);
  clearLeds();
}

void loop()
{
}

/*
  clear all leds of the strip
*/
void clearLeds()
{
  // loop through all leds and clear them
  for (uint16_t ledCnt = 0; ledCnt < NUM_LEDS; ledCnt++)
  {
    leds[ledCnt] = CRGB(0, 0, 0);
  }

  // show the result
  FastLED.show();
}

/*
  set the leds for a specified pattern
  In:
    zero based pattern number
*/
void setLeds(int patternNumber)
{
  Serial.print("Pattern "); Serial.println(patternNumber + 1);

  // loop through all leds for the given pattern
  for (uint16_t ledCnt = 0; ledCnt < NUMELEMENTS(patterns[patternNumber]); ledCnt++)
  {
    // a work copy for a led
    LEDINFO led;
    // retrieve from PROGMEM
    memcpy_P(&led, &patterns[patternNumber][ledCnt], sizeof(LEDINFO));

    // if the number is not zero, it's an 'active' led
    if (led.num != 0)
    {
      // led numbers are one-based, subtract 1 from num
      leds[led.num - 1] = CRGB(led.red, led.green, led.blue);
    }
  }
  // show the result
  FastLED.show();
}

I did a quick compile for a Mega with 50 patterns

Sketch uses 32840 bytes (12%) of program storage space. Maximum is 253952 bytes.
Global variables use 4784 bytes (58%) of dynamic memory, leaving 3408 bytes for local variables. Maximum is 8192 bytes.

In a later post I might get back with a way to select a pattern using the serial monitor. You can try that out yourself.

Note
I have no way to test the last code as I don’t have a Mega.