Simplifying control of LED 2801 square pixel

Hi there,

As say others, I'm a newbie, and want to create some 'very simple' Arduino code for controlling a strip of LED pixels on a string (of 20), 4 strings connected in total. My project will eventually monitor internet data and convert this into lighting particular LED modules (I'll save the ethernet/server bits for later when I have some development support).

The bit I'd love to work out now is how can I control each LED module in the string of 80 in such a way that I can write something like '1,7,9,25' to light LED number 1, 7, 9, and 25 in the string? Obviously intensity, time, colour, etc are also important but most of the tutorials out there seem to be using more pattern-based code (to create animations for example) rather than a basic identification and control of each module in the string. The variables i would love to control are:

  • Which pixel in the string I want to control
  • Whether it should be on/off
  • For how long it remains on
  • Fade in/out duration
  • Colour value

I will then look to control multiple LEDs in small groups. E.g. 3-7 LEDs at a time.

Any support or example code would be greatly appreciated so I can start experimenting with it to getting a better grasp. Things such as wiring up the Arduino, LEDs and power supply are already understood.

Many thanks!

You have one of the most used LED drivers to work with, the WS2801 (at least, I believe that's what you're referring to in your subject line.) Use FastSPI to control the string. You can leave it as one long string even (even if you cut it into four separate pieces, just connect the ends back together with wires.)

FastSPI keeps an array of the string:

NUM_LEDS = 20;
// there are some other needed lines here, but for this post they're irrelevant - get them from the FastSPI "testled" sketch.

leds[px].r = xxx;
leds[px].g = xxx;
leds[px].b = xxx;

Each 'px' refers to a specific pixel. So in your case, you'd want px = 0, 6, 8, and 24 - remember it always starts counting from 0 on, not 1.

FastSPI does work with the SPI port, so if you have other SPI devices, keep in mind that the 2801's don't have an ENable pin.

Your other option is to bit-bang your way there, which I've done also in similar fashion: keep an array of the led count, then address each LED by it's array index.

KirAsh4 - thanks very much!

I had just downloaded the FastSPI ZIP when i read your response. Your input still really helps though - particularly addressing specific LEDs as I hadn't worked that bit out.

I'll try implementing this and see how far I get.

Much appreciated.

No problem. Let us know if you run into issues or have more questions.

Hi KirAsh4,

If I may ask another question or two...

So I've had a go at putting the basic code together for a predefined set of LEDs to switch on, as per your suggestions... i hope.

I'm trying to figure out how to 'group' a set of LEDs so that I can control, for example, 5 modules at once. Is it an array that I need to create? Such as this:

LED led[group name] = {LED(2), LED(20), LED(47), LED(60)};

Essentially, where I'm heading for is that these 4 LEDs light up in blue, whilst all unspecified LEDs remain white, for example. And then another set of LEDs light up in blue, and the remaining 75 go white. All dependent upon the XML data feed from elsewhere (that bits for later).

I appreciate there must be ws2801 snippets/examples out there that I can find to build around - I will continue to hunt for them – but no success yet, most likely due to my level of understanding. Any pointers or examples very much appreciated.

Thanks again,

T

P.S. This is the code I have put together so far:

#include <FastSPI_LED.h>

#define NUM_LEDS 80

// Sometimes chipsets wire in a backwards sort of way
struct CRGB { unsigned char b; unsigned char r; unsigned char g; };
// struct CRGB { unsigned char r; unsigned char g; unsigned char b; };
struct CRGB *leds;

#define PIN 4

void setup()
{
FastSPI_LED.setLeds(NUM_LEDS);
FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2801);
FastSPI_LED.setPin(PIN);

FastSPI_LED.init();
FastSPI_LED.start();

leds = (struct CRGB*)FastSPI_LED.getRGBData();
}

void loop() {

//work out how to switch on/off groups - HIGH/LOW?,
//how to group different sets of LEDs - arrays?
//how to control the other 76 LEDs as one group as simply as possible
//how to fade in/out

leds[2].r = 255;
leds[2].g = 255;
leds[2].b = 255;
FastSPI_LED.show();
delay(3);
leds[20].r = 255;
leds[20].g = 255;
leds[20].b = 255;
FastSPI_LED.show();
delay(3);
leds[47].r = 255;
leds[47].g = 255;
leds[47].b = 255;
FastSPI_LED.show();
delay(3);
leds[60].r = 255;
leds[60].g = 255;
leds[60].b = 255;
FastSPI_LED.show();
delay(3);
}

Create an array with the led numbers you want to control, for example:byte BlueLEDgroup[] = {2, 20, 47, 60};
Then all you have to do is loop through that array:

for (p = 0; p < sizeof(BlueLEDgroup); p++) {
  // do something with BlueLEDgroup[p], for example
  BlueLedGroup[p].r = 0;
  BlueLedGroup[p].g = 0;
  BlueLedGroup[p].b = 255;
}

That will affect whatever is in the array at index 'p'. So the first iteration of that loop, p = 0 which translates to LED 2. The next iteration p = 1 which is LED 20. Etc., etc.

Thanks very much! Really appreciate your help on that.

It's currently giving me an error saying...

request for member 'b' in 'BlueLedGroup[p]', which is of non-class type 'byte''

But I'll try to figure that out.

Cheers

t

What's the code look like?

Probably like a chimp has been hacking away ;-). Here's where I've got to so far:

#include <FastSPI_LED.h>

#define NUM_LEDS 80

// Sometimes chipsets wire in a backwards sort of way
struct CRGB { unsigned char b; unsigned char r; unsigned char g; };
// struct CRGB { unsigned char r; unsigned char g; unsigned char b; };
struct CRGB *leds;

#define PIN 4

void setup()
{
FastSPI_LED.setLeds(NUM_LEDS);
FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2801);
FastSPI_LED.setPin(PIN);

FastSPI_LED.init();
FastSPI_LED.start();

leds = (struct CRGB*)FastSPI_LED.getRGBData();

}

void loop() {

byte BlueLEDgroup[] = {2, 20, 47, 60};

// BlueLedGroup ON
//have I declared 'p' correctly?
for (int p = 0; p < sizeof(BlueLEDgroup); p++) {
BlueLEDgroup[p].r = 0;
BlueLEDgroup[p].g = 0;
BlueLEDgroup[p].1b = 255;
}
//following array equals all LEDS that are not specified in BlueLedGroup (0-79 LEDs possible)
byte WhiteLEDgroup[] = {0, 1, 3-19, 21-46, 48-59, 61-79};

//WhiteLedGroup ON whilst BlueLedGroup is ON
for (int p = 0; p < sizeof(WhiteLEDgroup); p++) {
WhiteLEDgroup[p].r = 255;
WhiteLEDgroup[p].g = 255;
WhiteLEDgroup[p].b = 255;
}
}

Please use the code button to insert code in your posts. It looks like a #-sign.

TSD_80:
#define PIN 4

This is not needed for the WS2801 chips.

TSD_80:
FastSPI_LED.setPin(PIN);

Neither is this.

However, you do need a data rate line for the WS2801s. This isn't defined in the test program. Add this line within the setup() portion:

FastSPI_LED.setDataRate(2);

TSD_80:
BlueLEDgroup[p].1b = 255;

What's '1b' ?

TSD_80:
//following array equals all LEDS that are not specified in BlueLedGroup (0-79 LEDs possible)
byte WhiteLEDgroup[] = {0, 1, 3-19, 21-46, 48-59, 61-79};

That won't work for defining the remaining LEDs. You're better off not bothering with this second array. Just set everything to white first (you don't need an array for that), then cycle through the BlueLEDgroup array and turn those blue. When you're done with that, then you can call FastSPI_LED.show() which will turn the LEDs on.

Legend. Thanks once again. All comments will be addressed and are gratefully received. 1b was a typo alone. Great to know I can simplify the loop by having all default to white upfront too.

Sorry to be so demanding here. I've reviewed the code and added more to it - all code seems to verify with the exception of the same issue as before. I still get the error message 'LED_arrays_3.cpp: In function 'void loop()':
LED_arrays_3:48: error: request for member 'r' in 'BlueLEDgroup[p]', which is of non-class type 'byte'
LED_arrays_3:49: error: request for member 'g' in 'BlueLEDgroup[p]', which is of non-class type 'byte'
LED_arrays_3:50: error: request for member 'b' in 'BlueLEDgroup[p]', which is of non-class type 'byte''

for:

byte BlueLEDgroup[] = {2, 20, 47, 60};
// BlueLedGroup ON
for (int p = 0; p < sizeof(BlueLEDgroup); p++) {
  BlueLEDgroup[p].r = 0;
  BlueLEDgroup[p].g = 0;
  BlueLEDgroup[p].b = 255;
}
FastSPI_LED.show();
}

Right. While BlueLEDgroup holds the position of each pixel that you want to change to blue, it doesn't translate to what FastSPI needs. FastSPI keeps an array called 'leds' which is what you manipulate. That array contains all of the pixels in your string. Your BlueLEDgroup only holds those pixels you want changed, so you need to pass that information to the leds array, like so:

#include <FastSPI_LED.h>

#define NUM_LEDS 10

struct CRGB { unsigned char r; unsigned char g; unsigned char b; };
struct CRGB *leds;

byte BlueLEDgroup[] = {2, 20, 47, 60};
int done = 0;

void setup() {
  FastSPI_LED.setLeds(NUM_LEDS);
  FastSPI_LED.setChipset(CFastSPI_LED::SPI_WS2801);

  FastSPI_LED.setDataRate(2);
  
  FastSPI_LED.init();
  FastSPI_LED.start();

  leds = (struct CRGB*)FastSPI_LED.getRGBData(); 
}

void loop() {
  if (!done) {  // this stops the for loop from constantly being called
    for (int p = 0; p < sizeof(BlueLEDgroup); p++) {
      leds[BlueLEDgroup[p]].r = 0;
      leds[BlueLEDgroup[p]].g = 0;
      leds[BlueLEDgroup[p]].b = 0;
    }
    FastSPI_LED.show();
    done = 1;
  }
}

Now when you run this, 'p' becomes the counter from 0 to the size of the BlueLEDgroup array. Each time it iterates through it, it returns whatever is at that position in that array. So the first time, it returns the value '2'. When that gets passed to the 'leds' array, you get 'leds[2].r = 0', etc., etc. At the next iteration, p = 1, BlueLEDgroup[1] = 20, which when passed to the 'leds' array you get 'leds[20].r' etc., etc.

Does that make sense?

Yes it does. At least on initial read-through – I'll digest it over a few times so the array handling has sunk it and give it a try. It seems you've essentially packaged up /referenced the original LEDGroup code within the form of array that's understood by FastSPI.

In practical terms then, presumably if I create multiple LEDGroups I add them at the top of the code as you've done:

#include <FastSPI_LED.h>

#define NUM_LEDS 10

struct CRGB { unsigned char r; unsigned char g; unsigned char b; };
struct CRGB *leds;

byte BlueLEDgroup[] = {2, 20, 47, 60};
byte SecondLEDgroup[] = {x,x,x};
byte ThirdLEDgroup[] = {x, x, x, x, x};
int done = 0;

And then duplicate the Loop code against those as necessary:

void loop() {
//First group
  if (!done) {  // this stops the for loop from constantly being called
    for (int p = 0; p < sizeof(BlueLEDgroup); p++) {
      leds[BlueLEDgroup[p]].r = 0;
      leds[BlueLEDgroup[p]].g = 0;
      leds[BlueLEDgroup[p]].b = 0;
    }
    FastSPI_LED.show();
    done = 1;
  }
//SecondGroup
  if (!done) {  // this stops the for loop from constantly being called
    for (int p = 0; p < sizeof(SecondLEDgroup); p++) {
      leds[SecondLEDgroup[p]].r = 0;
      leds[SecondLEDgroup[p]].g = 0;
      leds[SecondLEDgroup[p]].b = 0;
    }
    FastSPI_LED.show();
    done = 1;
  }
//ThirdGroup
  if (!done) {  // this stops the for loop from constantly being called
    for (int p = 0; p < sizeof(ThirdLEDgroup); p++) {
      leds[ThirdLEDgroup[p]].r = 0;
      leds[ThirdLEDgroup[p]].g = 0;
      leds[ThirdLEDgroup[p]].b = 0;
    }
    FastSPI_LED.show();
    done = 1;
  }
}

Ever grateful. Thanks very much.

Bingo!

Keep in mind, I just added the 'done' part in my test program so it stops. Having built several battery-based LED displays, the less you have to invoke any kind of changes, the better. And one way to do that is to monitor how often you're sending data down the line. And if the lights aren't meant to change for several cycles, then why tell the program to do anything other than to wait outside of the loop. There's certainly no need to call FastSPI_LED() over and over again if nothing's changed.

Since you may have several "sections" of different colors, if they're all going to get set at the same time, you can run all those loops in one shot before jumping out and waiting.

Oh yeah, in your sample code, any loop after the first one will not run because you've set 'done' to 1 in the first one. So the second loop, when it checks whether done = 0 and finds it false, it skips the loop and moves on. This is what I mean with, if you're setting everything in one shot, you can encapsulate the whole series of loops with one break. If that's even necessary. Not knowing how you plan on implementing it, I don't really know what would work here.

Hi there,

Is there a specific reason for why FastSPI_LED.h is recommended for this task rather than SPI.h? There will be 80 LEDs in total.

Thanks

The SPI library is exactly that, a library to control the SPI functions on an Atmel. The FastSPI_LED however is a library written to control a bunch of different types of LED drivers, including the WS2801. So, if you only use the SPI library, you still have to write the various functions needed to control those LED drivers. Whereas with FastSPI_LED, it's already done for you. You just call the necessary functions.

Hi KirAsh4,

Thanks for your feedback. I now have the FastSPI doing what I want it too - hurray! The following step was to add Ethernet functionality so it's now calling a variable PHP file on a web server. As soon as that was added, we're getting 'disco lights' on the LEDs – specifically when it's connecting via the ethernet. Once the code is triggered the lights behave correctly, then go back to disco lights when its finished. This didn't happen when we're using the regular SPI but something on FastSPI sets it off. Is this a common problem that can be worked around? We're using an Arduino Ethernet so I'm wondering if there's a PIN conflict on the default 11 and 13... Could that be? Best

That is because the ethernet add-on that you're using is more than likely also using the SPI bus. And since the LED drivers don't have a select pin, just about anything that goes over the SPI bus will in some way or another mess with the LEDs. That's exactly what you're seeing. You have two options:

a) don't use FastSPI. Use bit-banging instead on two completely different pins, which will leave the SPI open for other devices, or
b) use a gate to control the LEDs, and you can still use FastSPI. It gets a bit trickier, but it can be done.

For my own project last year, I went the bit-bang route. I only had two strings of 20 LEDs each. I used the analog pins (of all things) to control the strings, A0 and A1 for one, and A2 and A3 for the other. Then I had an RF module on the SPI bus.

Note: bit banging IS slower than SPI, so depending on how fast you plan on sending data to the LEDs, bit-banging them may not work for you. For me it was plenty fast still.