Controlling several RGB LEDs in a nonblocking way

Hello,

I am an absolute newbie with Arduino, and I am facing a challenge.

With my 8x8 LED panel, I want to simulate blinking stars in the sky. I have changed a library example to take a random LED (of 64), give it a random colour, switch it on and then off after a few milliseconds. This is fine...but it doesn't really resemble blinking stars.

I would need

-a way to slowly fade the LED in from black...some kind of pulsating or breathing.

-a way to address more than one LED separately. At the moment, each run of the loop chosses one LED, lights it, switches it off, and then the loop runs again. This means that there can only be one LED lit at the same time, because of delay(). It would be nice if the second LED could fade in before the first LED has faded to black.

I heard that this can be done with nonblocking code, or with virtualDelay(). Have searched, but haven't found examples for this, so I am stuck here.

Who knows how to do this or point me to an example sketch?

Thanks,
Gerdski

A good tutorial on non blocking code is given by Nick Gammon. Read this first, and you'll understand the basics of non blocking code.

Then, a better way to do what you want is to do it as a state machine. I suggest another tutorial from the same Nick Gammon which is a little more complex but should help you a lot.

You can find here a lot of tutorials which can also help, especially those:

General design

  • Blink without delay (YouTube video) *
  • An 11-minute YouTube tutorial about state machines and blinking LEDs without using delay().
  • Planning and Implementing an Arduino Program
  • Tips for planning and organizing your program, from scratch.
  • How to do several things at a time
  • Tips for doing several things "at once" like testing switches, flashing LEDs, and controlling motors. Also illustrates the use of millis() to manage timing without blocking
  • How to do multiple things at once *
  • Another thread about doing multiple things "at the same time".
  • State machines *
  • Discussion about "state machines" which are ways of managing complex logic situations easily, by Nick Gammon.
  • State machines *
  • Another tutorial about state machines, by Mike Cook.

here's one to get you started as well:

Walk Away From Delay

Inspired by OP’s post, I wrote the following, and in turn this might give OP some inspiration.

I don’t have an rgb matrix, just an Uno with 6 leds on the PWM pins, so the code just randomly selects a pin to be active and turns it on at a certain brightness for a certain time. (V2 might fade them in…) Active pins are checked delay()-lessly to see if it’s time they died.

The stars’ details are in an array of structs:

struct stars
{
  byte pin;
  bool active;
  byte brightness;
  int onForHowLong;
  unsigned long wentOnAt;
};

Make adjustments here:

// all the stuff to do with randoms
long selectLimit = 10001; //star has a 1-in-selectLimit chance of becoming active
byte brightnessMin = 20; //else can't see
int onForHowLongMin = 100;
int onForHowLongMax = 5001;

The code:

// inspired by https://forum.arduino.cc/index.php?topic=651831
// 5 dec 2019

// twinkle, twinkle little star

//the pulse led
int pulseLedInterval = 500;
unsigned long previousMillisPulse;
bool pulseState = false;

struct stars
{
  byte pin;
  bool active;
  byte brightness;
  int onForHowLong;
  unsigned long wentOnAt;
};

const byte pins[] = {3, 5, 6, 9, 10, 11}; //uno pwm pins
const byte numberOfStars = sizeof(pins) / sizeof(pins[0]);
stars myStars[numberOfStars];

// all the stuff to do with randoms
long selectLimit = 10001; //star has a 1-in-selectLimit chance of becoming active
byte brightnessMin = 20; //else can't see
int onForHowLongMin = 100;
int onForHowLongMax = 5001;

void setup()
{
  Serial.begin(9600);
  Serial.println(__FILE__);
  Serial.println("TTLS");

  Serial.print("Initialising "); Serial.print(numberOfStars); Serial.println(" stars/pins");
  for (int i = 0; i < numberOfStars; i++)
  {
    myStars[i].pin = pins[i];
    pinMode(myStars[i].pin, OUTPUT);
    myStars[i].active = 0;
    myStars[i].brightness = 0;
    myStars[i].onForHowLong = 0;
    myStars[i].wentOnAt = 0;
    Serial.print("Star: "); Serial.print(i);
    Serial.print(", Pin: "); Serial.print(myStars[i].pin); Serial.print(", ");
    Serial.print(myStars[i].active);
    Serial.print(myStars[i].brightness);
    Serial.print(myStars[i].onForHowLong);
    Serial.println(myStars[i].wentOnAt);
    digitalWrite(myStars[i].pin, HIGH); delay(100); digitalWrite(myStars[i].pin, LOW);
  }

  //initialise pulse led
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, pulseState);

  randomSeed(analogRead(0));

  Serial.println("setup() done");
  Serial.println(" ");

  delay(2000);
}

void loop()
{
  doPulse();
  makeAStar();
  killAStar();
} //loop

void makeAStar()
{
  /*
    static int count = 0;

    Serial.print("Pass "); Serial.print(count); Serial.print(": ");
    for (int i = 0; i < numberOfStars; i++)
    {
    Serial.print(myStars[i].active);
    }
    Serial.println(" ");
  */
  
  for (int i = 0; i < numberOfStars; i++)
  {
    if (myStars[i].active != 1) //can only make a star that's not active at the moment
    {
      int starSelector = random(1, selectLimit);
      if (starSelector == 1) //we have a new star
      {
        myStars[i].active = 1;
        myStars[i].brightness = random(brightnessMin, 256);
        analogWrite(myStars[i].pin, myStars[i].brightness);
        myStars[i].onForHowLong = random(onForHowLongMin, onForHowLongMax);
        myStars[i].wentOnAt = millis();
        Serial.print("New star "); Serial.print(i); Serial.print(" of brightness "); Serial.print(myStars[i].brightness);
        Serial.print(", went on for ") ; Serial.print(myStars[i].onForHowLong); Serial.print(" at ") ; Serial.println(myStars[i].wentOnAt);
        //delay(1000); //to read
      }
      else
      {
        myStars[i].active = 0;
      }
    }
  }
  //count++;
}//makeAStar

void killAStar()
{
  for (int i = 0; i < numberOfStars; i++)
  {
    if (myStars[i].active == 1) //can only kill an active star
    {
      if (millis() - myStars[i].wentOnAt >= myStars[i].onForHowLong)
      {
        myStars[i].active = 0;
        digitalWrite(myStars[i].pin, LOW);
        Serial.print("  Killing star "); Serial.print(i); Serial.print(" at "); Serial.println(millis());
      }
    }
  }
}//killAStar

void doPulse()
{
  if (millis() - previousMillisPulse >= pulseLedInterval)
  {
    previousMillisPulse = millis();
    pulseState = !pulseState;
    digitalWrite(LED_BUILTIN, pulseState);
  }
} //doPulse

I have changed a library example to take a random LED (of 64), give it a random colour, switch it on and then off after a few milliseconds. This is fine...but it doesn't really resemble blinking stars.

If you post this example, i can make suggestions on how to alter this and modify the my 'millis() fade-code' to suit the requirements. What kind of 8x8 led panel do you have ?

Hello all,

big sorry for replying so late, and thanks a lot for your answers.

First of all: I have a 8x8 WS2812 Panel that is obviously broken..many of the LEDs don't work. Replacement is on the way from china. There seems to be an issue with this set of Panels, other people here report similar errors.

I have changed it now to an 8x1 that I already had, and it runs better now.

So, now I am ready to test your suggested Sketches. :slight_smile:

Here is my Sketch now, breathing in white on an 8x8, this is fine. I am thinking about how to fade the colours, which are randomized but not used yet.

I took the basics to this from somewhere,and now I see that it features the Adafruit Library. Should be no problem though…once the nonblocking structure is in place, i should be able to replace the commands…

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>

#define PIN            6
#define NUMPIXELS      8


Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int rval;
int gval;
int bval;
int delayval1;
int delayval2;
int shortdelay;
int intensity = 40;
int pixnr;
int pulsesteps=20;
int jstep=(intensity/pulsesteps);

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
 
  pixels.begin();
}

void loop() {
  for(int i=0;i<NUMPIXELS;i++){
    rval = random(0,intensity);
    gval = random(0,(intensity-rval));
    bval = random(0,(intensity-gval-rval));
    pixnr=random(0,(NUMPIXELS-1));
    delayval1 = random(200,600);
    delayval2 = random(200,600);
    shortdelay=50;
 
    for(int j=0; j<intensity; j+=j+jstep) {
      pixels.setPixelColor(pixnr, pixels.Color(j,j,j)); 
      pixels.show(); 
      delay(shortdelay);
    }
    for(int j=intensity; j>0; j=j-jstep) {
      pixels.setPixelColor(pixnr, pixels.Color(j,j,j)); 
      pixels.show(); 
      delay(shortdelay);
    }

     pixels.setPixelColor(pixnr, pixels.Color(0,0,0)); 
    pixels.show(); 

    delay(20);    
  }
  
}

12stepper wrote:

I don't have an rgb matrix, just an Uno with 6 leds on the PWM pins

Yes, I see...maybe I can change the code to fit to LED Sticks or Panels or Rings.

As a Software Developer Libraries are nothing new to me. I think they are fine a slong as they don't overload or slow program execution or you get dependent on these things and can never work without them any more.

I believe one can translate

pixels.setPixelColor(pixnr, pixels.Color(j,j,j));
pixels.show();

to

digitalWrite(ledPin, HIGH);

somehow (and vice versa)

Hello all,

thank you for your replies. Since a few days now, I try to get the nonblocking conept, but somehow I fail to understand it fully.

Here is a very good explanation in German: Timer mit Arduino – Alternative zu Delays

There's an example in it which is very close to what I need:

const int ledPin1 =  10;
const int ledPin2 =  8;

int ledState1 = LOW;
long myTimer1 = 0;
long myTimeout1 = 700;

int ledState2 = LOW;
long myTimer2 = 0;
long myTimeout2 = 200;


void setup() {
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
}

void loop() {
  if (millis() > myTimeout1 + myTimer1 ) {
    myTimer1 = millis();

    if (ledState1 == LOW) ledState1 = HIGH;
    else ledState1 = LOW;

    digitalWrite(ledPin1, ledState1);
  }

  if (millis() > myTimeout2 + myTimer2 ) {
    myTimer2 = millis();

    if (ledState2 == LOW) ledState2 = HIGH;
    else ledState2 = LOW;

    digitalWrite(ledPin2, ledState2);
  }
}

I can change this to the Adafruit Library version of

pixels.setPixelColor(pixnr, pixels.Color(r,g,b));
pixels.show();

...but how can I change the on/off switch (LOW/HIGH) to a slow fading in and fading out to a random colour? There would have to be a series of RGB Values increasing, then short waiting time, then increasing again, until the max value is reached, then wait again, then decreasing. (When at Zero, pick another random LED, but that's a different problem)

Maybe the concept of this code is not made for pulsating RGB Leds, jsut to swith them on or off. Or is there an easy way to change it?

Thanks a lot!

Yeah this way it is a tad tricky and only allows for 2 states, Basically there are 2 ways of using millis() for timing, and one way seems to be dominant in examples (in my opinion the inferior method)
Here is an example of what i mean:

#define LEDPIN 10  // on an UNO that's a PWM pin
#define CYCLE 4000

void setup() {
  pinMode(LEDPIN, OUTPUT);
}

void loop() {
  uint32_t phase = millis() % CYCLE; // use the modulo of elapsed time 
  if (phase > (CYCLE / 2) ) phase = CYCLE - phase; // if we are past haflway in the cycle 
                                                  // we should be on the way back
  uint8_t outvalue = map(phase, 0, CYCLE / 2, 0, 255);  // use the map function.
  analogWrite(LEDPIN, outvalue);
}

Now instead of measuring time until enough time has elapsed to take another step, you can measure time and determine which step you should be at.
I used the map() function to convert the value.
This method can be used also for fading between colors (in fact then you should fade Red, Green & Blue values individually )

Thank you for your reply. It is not easy to understand.

Would you say that duplicating the loop statements could make 2 different LEDs pulsate in different speeds?

like

uint32_t phase1 = millis() % CYCLE1; // use the modulo of elapsed time
if (phase > (CYCLE1 / 2) ) phase1 = CYCLE1 - phase1; // if we are past haflway in the cycle
                                                  // we should be on the way back
uint8_t outvalue1 = map(phase1, 0, CYCLE1 / 2, 0, 255);  // use the map function.
analogWrite(LEDPIN1, outvalue1);

uint32_t phase2 = millis() % CYCLE2; // use the modulo of elapsed time
if (phase > (CYCLE2 / 2) ) phase2 = CYCLE2 - phase2; // if we are past haflway in the cycle
                                                  // we should be on the way back
uint8_t outvalue2 = map(phase2, 0, CYCLE2 / 2, 0, 255);  // use the map function.
analogWrite(LEDPIN2, outvalue2);

And how woud I pulsate / fade to a random RGB colour?

Thanks a lot!

Would you say that duplicating the loop statements could make 2 different LEDs pulsate in different speeds?

Yes i would ! the system is not complex once you understand how it works. A cycle takes a certain amount of time, within that time it fades to it maximum value and back. By simply talking the modulo we remove all completed cycles from the elapsed time and can there determine at which point of the cycle we are, in this case we fade in and out, so halfway through the cycle we want to start counting back so to say.
As long as the cycle length remains the same, it becomes less important how much time we use between frames (when we send our outvalue) . Tricky is when we want to modify our cycle length / change our speed and don't want the brightness to 'skip' to the new brightness level that corresponds to the new point in the new cycle (but maybe more about that later)

And how woud I pulsate / fade to a random RGB colour?

Firts, what do you define as a random RGB color ? if you randomly choose an R, G & B value between 255 for each, chances are you will get a color which will look sort of whitish. Since that is rarely what you want, i ask.
Second. Look at the map function again

nowColor.R = map(phase, 0, CYCLE / 2, startColor.R, endColor.R);
nowColor.G = map(phase, 0, CYCLE / 2, startColor.G, endColor.G);
nowColor.B = map(phase, 0, CYCLE / 2, startColor.B, endColor.B);

Hello all,

sorry for not replying for such a long time. But I was successful in writing a Sketch that does what I want:

For any amount of available Pixels:
-choosing one of the available Pixels (of 8, 64, or more)
-getting a random colour, avoiding the all-white-problem
-fading this colour in and out
-when fading out is done find another available pixel and do it all again

It works for me with an 8-Pixel-Stick and an 8x8 Matrix.

Here’s the code:

#include <Adafruit_NeoPixel.h>
#include <avr/power.h>

#define PIN            6
#define NUMPIXELS      8
#define WIEVIELE       5
#define TIMEOUTMIN     100
#define TIMEOUTMAX 	  600
#define BLINKSPEEDMIN  10
#define BLINKSPEEDMAX  100


Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int rval;
int gval;
int bval;
float intensity = 10.0;
int pulsesteps=20;

int ledState[WIEVIELE];
long myTimer[WIEVIELE];
long myTimeout[WIEVIELE];
int pixnr[WIEVIELE];
int r[WIEVIELE];
int g[WIEVIELE];
int b[WIEVIELE];
float rstep[WIEVIELE];
float bstep[WIEVIELE];
float gstep[WIEVIELE];
int rnow[WIEVIELE];
int bnow[WIEVIELE];
int gnow[WIEVIELE];
int pixnrneu[WIEVIELE];
int loopcounter[WIEVIELE];


void setup() {
   pinMode(LED_BUILTIN, OUTPUT);
   randomSeed(analogRead(0));
   //Serial.begin(9600);
   pixels.begin();
   for (int j=0; j<WIEVIELE; j=j+1) {
   ledState[j]='end';
   myTimer[j]=0;
   myTimeout[j]=random(TIMEOUTMIN,TIMEOUTMAX);
   pixnr[j] = random(1,NUMPIXELS);
   r[j] = 0;
   g[j] = 0;
   b[j] = 0;
   rstep[j] = 0;
   gstep[j] = 0;
   bstep[j] = 0;
   rnow[j] = 0; 
   gnow[j] = 0; 
   bnow[j] = 0; 
   loopcounter[j] = 0;
  }
}

void loop() {

	for (int i = 0; i < WIEVIELE; i = i + 1) {
    
	  if (millis() > myTimeout[i] + myTimer[i] ) {
	    myTimer[i] = millis();

	    if (ledState[i]=='start' ) {
			// Neue PixNr
			boolean found = true;
			while (found == true) {
				pixnrneu[i] = random(1,NUMPIXELS);
				found = false;
				for (int j = 0; j < WIEVIELE; j = j + 1) {
					if (pixnr[j] == pixnrneu[i]) found = true;
				}
			}
			if (found==false) pixnr[i]=pixnrneu[i]; 
			// / Neue PixNr

			myTimeout[i] = random(TIMEOUTMIN,TIMEOUTMAX);
			r[i] = random(0,intensity);
			g[i] = random(0,(intensity-r[i]));
			b[i] = random(0,(intensity-g[i]-r[i]));
			ledState[i] = 'up';
	    }
	    else if (ledState[i] == 'up') {
	       myTimeout[i] = random(BLINKSPEEDMIN,BLINKSPEEDMAX);
	       loopcounter[i] += 1;
	       rstep[i] = (r[i]/intensity);
	       gstep[i] = (g[i]/intensity);
	       bstep[i] = (b[i]/intensity);
	       if (rnow[i]<=r[i]) rnow[i]=loopcounter;
	       if (gnow[i]<=g[i]) gnow[i]=loopcounter;
	       if (bnow[i]<=b[i]) bnow[i]=loopcounter;
	       rnow[i] =rstep[i]*loopcounter[i];
	       gnow[i] =gstep[i]*loopcounter[i];
	       bnow[i] =bstep[i]*loopcounter[i];
	       if (loopcounter[i] == intensity)
	       ledState[i] = 'down';
	    }
	    else if (ledState[i] == 'down') {
	      myTimeout[i] = random(BLINKSPEEDMIN,BLINKSPEEDMAX);
	       loopcounter[i] -= 1;
	       rstep[i] = (r[i]/intensity);
	       gstep[i] = (g[i]/intensity);
	       bstep[i] = (b[i]/intensity);
	       if (rnow[i]<=r[i]) rnow[i]=loopcounter;
	       if (gnow[i]<=g[i]) gnow[i]=loopcounter;
	       if (bnow[i]<=b[i]) bnow[i]=loopcounter;
	       rnow[i] =rstep[i]*loopcounter[i];
	       gnow[i] =gstep[i]*loopcounter[i];
	       bnow[i] =bstep[i]*loopcounter[i];
	       if (loopcounter[i] == 0)
	       ledState[i] = 'start';
	    }
	    else if (ledState[i]=='end' ){
	      myTimeout[i]=0;
	       r[i] = 0;
	       g[i] = 0;
	       b[i] = 0;
	       ledState[i] = 'start';
	    }

	    pixels.setPixelColor(pixnr[i], pixels.Color(rnow[i],gnow[i],bnow[i])); 
	    pixels.show(); // This sends the updated pixel color to the hardware.
  		}
  	}
  
}

The define-commands at the beginning are:

PIN 6 → where your LEDs are attached on the Arduino?
NUMPIXELS 8 → How many LEDs do you have altogether?
WIEVIELE 5 → How many of them do you want to blink? Here we have 5 of 8 blinking
TIMEOUTMIN 100, TIMEOUTMAX 600 → Time between end of blinking and choosing of new Pixel (Randomized)
BLINKSPEEDMIN 10, BLINKSPEEDMAX 100 → How fast should it blink. Randomized

float intensity = 10.0 is the brightness. I set it to less bright.

It needs the Adafruit Neopixel Library.

As this is my first really working sketch that does something useful, the code is obviously far from perfect. But…yes…it works, and it looks nice in a dark room. Will improve it for a better lightshow…