Best practice, conditions in loops

Hi - I wonder if the forum could help me learn a better, more efficient method to accomplish this task?

I want to use a neoPixel strip as part of a gauge. I'm using a neoPixel ring (24 LEDs) but I want to use different ring segments for different measurements. I have incorporated an offset so I can call the function and start at LED x and then sweep for y.

this use case is: shows either an increasing trend in temperature or a decreasing one.
I need to sweep/chase the LED from left to right (increase) and right to left (decrease)

I would like to improve the look of the chase effect by dimming the trailing LEDs where leading LED is 100%, -1 = 75%, -2 = 50% , -3 = 25% and -4 = 0% (being able to adjust these for best effect. [Yep i was going for 80's throwback 'KITT car' style :grinning:]

This code functions as expected, but I think there are smarter ways to achieve it.
Could anyone offer advice on how I should improve this, please?

Future thought. This also locks up the code while it sweeps, so making it non-blocking but maintaining timing through each sweep would be a nice addition

NB code adapted from Adafruit NeoPixel Library example.
NBB I'm testing with a 10 pixel strip (not 24) so avoiding the offset and sweep range for now.

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#define LED_COUNT 10 // Number of pixels on the strip
#define LED_PIN 13

// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

void sweepfwd(uint32_t r, uint32_t g, uint32_t b, int wait, int offset, int PIX_COUNT)
{
  float dim1 = 0.65;
  float dim2 = 0.4;
  float dim3 = 0.15;

  for (int j = offset; j < PIX_COUNT + offset; j++)
  {
    strip.clear();
    strip.setPixelColor(j + offset, strip.Color(r, g, b)); // Set pixel 'c' to value 'color'
    for (int i = 0; i < 4; i++)
    {
      if (j > 0)
      {
        strip.setPixelColor(j + offset - 1, strip.Color(r * dim1, g * dim1, b * dim1)); // Set pixel 1 behind to value 'color' at dim1 brightness
      }
      if (j > 1)
      {
        strip.setPixelColor(j + offset - 2, strip.Color(r * dim2, g * dim2, b * dim2)); // Set pixel 2 behind to value 'color' at dim2 brightness
      }
      if (j > 2)
      {
        strip.setPixelColor(j + offset - 3, strip.Color(r * dim3, g * dim3, b * dim3)); // Set pixel 3 behind to value 'color' at dim3 brightness
      }
      if (j >= 3)
      {
        strip.setPixelColor(j + offset - 4, strip.Color(0, 0, 0)); // Set pixel 'c' to value 'color'
      }
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  }
}

void sweepbwd(uint32_t r, uint32_t g, uint32_t b, int wait, int offset, int PIX_COUNT)
{
  float dim1 = 0.65;
  float dim2 = 0.4;
  float dim3 = 0.1;
  int colour = 0;
  int PIX_START = PIX_COUNT + offset;

  for (int j = offset; j < PIX_START; j++)
  {
    strip.clear();
    colour = strip.Color(r, g, b);
    strip.setPixelColor(PIX_START - j, colour); // Set pixel 'c' to value 'color'
    for (int i = 0; i < 4; i++)
    { // conditional test of LED to make sure we don't turn on any pixel before
      if (j > 0)
      {
        colour = strip.Color(r * dim1, g * dim1, b * dim1);

        strip.setPixelColor(PIX_START - j + 1, colour); // Set pixel 1 behind to value 'color' at dim1 brightness
      }
      if (j > 1)
      {
        colour = strip.Color(r * dim2, g * dim2, b * dim2);

        strip.setPixelColor(PIX_START - j + 2 , colour); // Set pixel 2 behind to value 'color' at dim2 brightness
      }
      if (j > 2)
      {
        colour = strip.Color(r * dim3, g * dim3, b * dim3);

        strip.setPixelColor(PIX_START- j +3 , colour); // Set pixel 3 behind to value 'color' at dim3 brightness
      }
      if (j >= 3)
      {
        colour = 0;

        strip.setPixelColor(PIX_START - j + 4, colour); // Set pixel 4 behind to off
      }
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  }
}

void setup()
{
  Serial.begin(115200);
  strip.begin();
  strip.show();
  strip.setBrightness(50);
}

void loop()
{
  sweepfwd(255, 255, 255, 75, 0, LED_COUNT);
  delay(1);
  sweepbwd(255, 255, 255, 75, 0, LED_COUNT);
  delay(1);
}

If "locking up" the code will be an issue (which it most likely will because it almost always will) then you should not spend any more time on this implementation. To implement without delays you will have to restructure the code anyhow. I would recommend implementing with millis() for timing and a state machine.

Start looking at the following tutorials:

State Machine

Example-code for timing based on millis()

What is the purpose of this for loop (in both functions)? It just does the same thing 4 times.

humm how to explain the intent..
(avoiding offsetting for a min)
itteration 1 - set 1st LED to 255
Itteration 2 - set 2nd LED to 255 and 1st LED to 75% of 255
Itteration 3 - set 3rd LED to 255, 2nd 75%, 1st 50%
Itteration 4 - set 4th LED to 255, 3rd LED to 75%, 2nd 50%, 1st 25%
Itteration 5 - set 5th LED to 255, 4th LED to 75%, 3rd 50%, 2nd 25%, 1st 0%

The intent of the loop was to only set the trailing LEDs when the lead LED is forward

I mocked up this table, when initially trying to work this out. If 255 = ON (leading LED) and 191 = 75% 2st trailing LED etc. the sequence would look like:

|1|255|0|0|0|0|0|0|0|0|0|0|
|2|191|255|0|0|0|0|0|0|0|0|0|
|3|127|191|255|0|0|0|0|0|0|0|0|
|4|64|127|191|255|0|0|0|0|0|0|0|
|5|0|64|127|191|255|0|0|0|0|0|0|
|6|0|0|64|127|191|255|0|0|0|0|0|
|7|0|0|0|64|127|191|255|0|0|0|0|
|8|0|0|0|0|64|127|191|255|0|0|0|
|9|0|0|0|0|0|64|127|191|255|0|0|
|10|0|0|0|0|0|0|64|127|191|255|0|

So a full sweep is completed in 10 steps but I only want to alter the value of a defined number of LEDs. e.g. I want adjust LEDs 4,5,6,7,8,9. So offset is 4, PIX_COUNT = 6

I get what the outer j loop is doing. However, the inner i loop does the same thing 4 times! i is not used during the loop and j never changes during the loop.

[as I'm writing, I think the penny is dropping]

Hi yes - but aren't j - 1 j - 2 j - 3 and j - 4 only set based on the current value of J
so if J = 0 it passes through and does nothing...

So I'm guessing your point is the inner loop does nothing, apart from wasting time checking the same conditions 4x so just have the conditions in the main loop??

Yes. Remove the for loop and keep the body of the loop. Look at it closely. All of the variables used are the same for all 4 iterations:

    for (int i = 0; i < 4; i++)
    {
      if (j > 0)
      {
        strip.setPixelColor(j + offset - 1, strip.Color(r * dim1, g * dim1, b * dim1)); // Set pixel 1 behind to value 'color' at dim1 brightness
      }
      if (j > 1)
      {
        strip.setPixelColor(j + offset - 2, strip.Color(r * dim2, g * dim2, b * dim2)); // Set pixel 2 behind to value 'color' at dim2 brightness
      }
      if (j > 2)
      {
        strip.setPixelColor(j + offset - 3, strip.Color(r * dim3, g * dim3, b * dim3)); // Set pixel 3 behind to value 'color' at dim3 brightness
      }
      if (j >= 3)
      {
        strip.setPixelColor(j + offset - 4, strip.Color(0, 0, 0)); // Set pixel 'c' to value 'color'
      }
    }

ok - you may need to point them out as I'm looking closely
Here is what I think is happening.
The for loop here is to handle 3 trailing leds where the brightness is 25% less each time and the 4th is off
j is the led number on neoPixel.
when j = 1 then j - 1 is set to (r, g and b all multiplied by dim1)
when j = 2 then j - 2 is set to (r, g and b all multiplied by dim2)
when j = 4 then j - 3 is set to (r, g and b all multiplied by dim3)
when j = 4 or higher then j - 4 is set to (r, g and b = 0)

I get the point that 'i' is only used to itterate 4 times, before exiting and running the outer loop (j) again.

So should this for statement use 'j' as an upper limit with logical and?

e.g.

for (int i = 0; i < j && j < 4 ; i++){
}

or

for (int i = j; i  > 0 && j <4; i++){
}

???

so revised functions, only iterating the inner loop as needed + this now cycles through to all off

void sweepfwd(uint32_t r, uint32_t g, uint32_t b, int wait, int offset, int PIX_COUNT)
{
  float dim1 = 0.65;
  float dim2 = 0.4;
  float dim3 = 0.15;

  for (int j = offset; j < PIX_COUNT + offset; j++)
  {
    strip.clear();
    strip.setPixelColor(j + offset, strip.Color(r, g, b)); // Set pixel 'c' to value 'color'
    for (int i = 0; i < j; i++){
      if (j > 0 )
      {
        strip.setPixelColor(j + offset - 1, strip.Color(r * dim1, g * dim1, b * dim1)); // Set pixel 1 behind to value 'color' at dim1 brightness
      }
      if (j > 1)
      {
        strip.setPixelColor(j + offset - 2, strip.Color(r * dim2, g * dim2, b * dim2)); // Set pixel 2 behind to value 'color' at dim2 brightness
      }
      if (j > 2)
      {
        strip.setPixelColor(j + offset - 3, strip.Color(r * dim3, g * dim3, b * dim3)); // Set pixel 3 behind to value 'color' at dim3 brightness
      }
      if (j >= 3)
      {
        strip.setPixelColor(j + offset - 4, strip.Color(0, 0, 0)); // Set pixel 'c' to value 'color'
      }
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  
  }
}

void sweepbwd(uint32_t r, uint32_t g, uint32_t b, int wait, int offset, int PIX_COUNT)
{
  float dim1 = 0.65;
  float dim2 = 0.4;
  float dim3 = 0.1;
  int colour = 0;
  int PIX_START = PIX_COUNT + offset;

  for (int j = offset; j < PIX_START; j++)
  {
    strip.clear();
    colour = strip.Color(r, g, b);
    strip.setPixelColor(PIX_START - j, colour); // Set pixel 'c' to value 'color'
    for (int i = 0 ; i < j; i++)
    { // conditional test of LED to make sure we don't turn on any pixel before
      if (i > 0)
      {
        colour = strip.Color(r * dim1, g * dim1, b * dim1);

        strip.setPixelColor(PIX_START - j + 1, colour); // Set pixel 1 behind to value 'color' at dim1 brightness
      }
      if (i > 1)
      {
        colour = strip.Color(r * dim2, g * dim2, b * dim2);

        strip.setPixelColor(PIX_START - j + 2, colour); // Set pixel 2 behind to value 'color' at dim2 brightness
      }
      if (i > 2)
      {
        colour = strip.Color(r * dim3, g * dim3, b * dim3);

        strip.setPixelColor(PIX_START - j + 3, colour); // Set pixel 3 behind to value 'color' at dim3 brightness
      }
      if (i >= 3)
      {
        colour = 0;

        strip.setPixelColor(PIX_START - j + 4, colour); // Set pixel 4 behind to off
      }
    }
    strip.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
  }
}

What you just described is being done 4 times in a row for every iteration of j. To illustrate try this, comment out this one line in the sweepbwd and sweepfwd functions (leave the code block for the loop):

for (int i = 0; i < 4; i++)

You will see that you get EXACTLY the same behavior on the NeoPixel...which means the for loop is unnecessary.

You will still be performing the same lines of code over and over again! Look, the bottom line is that the variables j, offset, r, g, b, dim1, dim2, and dim3 do not change in this for loop and i is not used in the loop block which means you are executing the exact same calls with the exact same arguments every iteration!

I ripped the below from a larger sketch. It illustrates an approach that is easy and effective.

It runs the loop at 50 Hz, the frame or "refresh" rate.

In every loop() pass, turn on any pixels you want on. But never turn them off.

Instead, a function is called at the frame rate to gradually fade out all the LEDs. Obvsly once they reach 0 black, they are left there.

In this demo, the frame rate is counted up until it's time to move a traveling LED along. At that time, the next LED is turned on.

Over the next N number of frames, that LED will fade to zero.

A set of constants controls how fast the LED moves, how long it takes to fade away and the refresh rate. Keep the refresh rate high, like 50 Hz.

This has only one traveller at one speed, but you can add more, of different colours, speeds and direction. All will leave trails.

You'll have plenty of time in the loop to do any calculations informing which pixels should be turnt on. Yuo can do those at the full free running loop rate, the refresh rate or indeed as I have done here at a counted fraction of the refresh rate.

Key: for motion to look good, don't skip over pixels when moving something along.

Trick: if separate travellers stick to one R, G or B channel, two travellers can pass right through or by each other, and you get a mixed colour, all fading looks tots plausible.

Handling LEDs that use more than one R, G or B can be done but it starts to make the code trickier - I have reduced and captured just the essence of the idea here as possible food for thought.

# include <Adafruit_NeoPixel.h>

# define neoPIN   8
# define nPixels  24

Adafruit_NeoPixel theStrip = Adafruit_NeoPixel(nPixels, neoPIN, NEO_GRB + NEO_KHZ800);

void setup() {
  Serial.begin(115200);
  Serial.println("Jello Whirled!\n");

  theStrip.begin();
}

unsigned char traveller;    // index of moving LED
unsigned long lastTime;     // regulate loop to frame rate

# define FRAME  20    // frame time in ms
# define STEP   10    // frames per step
# define FADE   3     // fade amount per frame

unsigned char bigTick;// count STEP to action

void loop() {
  unsigned long now = millis();

  if (now - lastTime < FRAME)
    return;

  lastTime = now;

  if (++bigTick >= STEP) {
    traveller++;
    if (traveller >= nPixels) traveller = 0;
  
    theStrip.setPixelColor(traveller, 0x0000ff);
    bigTick = 0;
  }

  theStrip.show();

  ageLEDs();
}

// reduce all RGB of all LEDs just a little bit
void ageLEDs()
{
	unsigned char *blast = theStrip.getPixels();

	for (int ii = 0; ii < (nPixels << 2); ii++, blast++) {
		if (*blast < 3) *blast = 0;
		else *blast -= 3;
	}
}

Tinker with it in the wokwi simulator, but it looks way cooler IRL.

a7

Hi Alto777 - may thanks for this snip, really useful to help me consider the solution from different angles. The effect produced is very nice.
BTW the sim is great tool!

@alto777 - i have tied to expand your example a little. The aim, two travellers able to run independently and only have the agedLEDs applied to the traveller LEDs. I using a 24 LED ring and I'm looking for pix 3 - 9 for traveller1 and pix 15-21 for traveller2 pix 0, 1, 12, 23 are used as indicators and the remaining pixels, are unused for now.

the code below, works very well for traveller1 and the first pass of traveller2 functions fine but the second call of agedLED() isn't working, so I'm clearly not understanding this function correctly.

what I thought should happen is the routing gets the pointer to the raw neopixel buffer (*blast) the offset is added as the pointer will be index 0 for pix0
The loop is iterated for the number of pixels in the traveller (end - start)

As the wokwi only has a 16 LED ring I have adjusted the code to test in the sin (great link thanks!)

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#define LED_COUNT 16 // set to test in wokwi
#define LED_PIN 13
// Declare our NeoPixel strip object:
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

unsigned char traveller1; // index of moving LED
unsigned char traveller2; // index of moving LED
unsigned long lastTime;  // regulate loop to frame rate
unsigned long passTime1;
unsigned long passTime2;
#define FRAME 14 // frame time in ms
#define STEP 8   // frames per step
#define FADE 9   // fade amount per frame
#define start1 1
#define end1 7
#define start2 10
#define end2 14
unsigned long pass1 = (STEP * (end1 - start1) * FRAME); // used to only flip direction of traveller  when a full pass has compelted
unsigned long pass2 = (STEP * (end2 - start2) * FRAME);

unsigned char bigTick; // count STEP to action
unsigned char fullPass;
bool direction1 = true;
bool direction2 = true;

// reduce all RGB of all LEDs just a little bit
void ageLEDs(unsigned char start, unsigned char end)
{
  unsigned char *blast = strip.getPixels() + start;

  for (int ii = start; ii <= ((end - start) << 2); ii++, blast++)
  {
    if (*blast < FADE)
      *blast = 0;
    else
      *blast -= FADE;
  }
}


void setup()
{
  Serial.begin(115200);
  strip.begin();
  strip.show();
  strip.setBrightness(191);
}

unsigned char chase(unsigned char start, unsigned char end, unsigned char traveller, bool dir)
{
  traveller++;
    if (traveller >= end - start)

      traveller = 0;
    int colour = strip.Color(255, 0, 0);
    int pix = dir ? traveller + start : end - traveller;
    strip.setPixelColor(pix, colour);
    return traveller;
}

void loop()
{
  unsigned long now = millis();

  if (now - lastTime < FRAME)
    return;

  lastTime = now;
  if (++bigTick >= STEP)
  {
    traveller1 = chase(start1, end1, traveller1, direction1);
    traveller2 = chase(start2, end2, traveller2, direction2);

    bigTick = 0;
  }

  strip.show();
  ageLEDs(start1, end1);
  ageLEDs(start2, end2); // this second call to age the traveller2 pixel isn't working as expected.

  if (now - passTime1 < pass1)
    return;
  passTime1 = now;
  direction1 = !direction1;

  if (now - passTime2 < pass2)
    return;
  passTime2 = now;
  direction2 = !direction2;
}

Pro tip: you can edit the diagram.json file in the wokwi and make a ring any size you want!

An admirable effort! It looks like you can think your way through this kind of thing. I can see where you are heading.

But it looks like you got out over your skis a little bit.

And you are going to love learning how to handle two things that are almost alike - adding a third traveller to your scheme will be a PITA if you keep duplicating code.

For now, however, I will help you clean up and get two travelers traveling in the literal fashion you have naturally adopted.

You have misunderstood at the lowest level the intent of ageLEDs.

It is a global approach to making all the LEDs on the strip into sorta incandescent lights - when you turn them on, they are on. When you don't keep turning them on, they fade to black slowly.

All LEDs. So just one call to ageLEDs() is necessary for the entire system. My original as it was.

Now your travelers just bounce back and forth. It looks like you have succeeded in seeing the boundaries and reversing direction. If you tinkered with the gross timing constants for the desired speeds, you did that correctly, or maybe the original values were OK.

I made a 24 pixel ring, removed your brightness call and fixed the logic at the end of you loop() which appears to handle the reversal.

The below works, after a fashion, but the way that you detect and perform reversal goes bad after awhile. You have managed to over-complicate the matter by calculating the time at which a reversal must occur - this is silly (sorry!), you can just reverse when… wait for it… a traveller reaches the endpoint in the direction it is traveling. You don't need to worry when that happens, only see that it has happened.

I'm outta juice right now, but I think what you can do is move the reversal logic (last two if/else statements in you loop() ) into the bigTick code - that is after you call chase for each traveller, then figure whether to reverse. You can use the same logic, please do use exactly the same logic, but figure out what the values of pass1 and pass2 should be based on how many bigTicks it will take to get to the point where reversing is needed.

unsigned long pass1 = (STEP * (end1 - start1) * FRAME);  // no - some number of bigTicks!
unsigned long pass2 = (STEP * (end2 - start2) * FRAME);  // same same - just end - start ?

This is where everyone and their mothers will be screaming about way better approaches to having multiple travelers. There are multiple ways to do this of varying levels of sophistication. As I said, you are going to love it when you bag those skills.

But try it first just the way you did except test for, and do, the reversal on the bigTick.

Oh, and if you do progress and need help, post the link to your wokwi as well as posting the code here.

//#include <Arduino.h>
# include <Adafruit_NeoPixel.h>
# define LED_COUNT 24 // set to test in wokwi
# define LED_PIN 8
// Declare our NeoPixel strip object:

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);


unsigned char traveller1; // index of moving LED
unsigned char traveller2; // index of moving LED

unsigned long lastTime;  // regulate loop to frame rate
unsigned long passTime1;
unsigned long passTime2;
#define FRAME 14 // frame time in ms
#define STEP 8   // frames per step
#define FADE 9   // fade amount per frame
#define start1 0
#define end1 10
#define start2 12
#define end2 23

unsigned long pass1 = (STEP * (end1 - start1) * FRAME); // used to only flip direction of traveller  when a full pass has compelted
unsigned long pass2 = (STEP * (end2 - start2) * FRAME);

unsigned char bigTick; // count STEP to action
unsigned char fullPass;

bool direction1 = true;
bool direction2 = true;

// reduce all RGB of all LEDs just a little bit
void ageLEDs()
{
	unsigned char *blast = strip.getPixels();

	for (int ii = 0; ii < (LED_COUNT * 3); ii++, blast++) {
		if (*blast < 3) *blast = 0;
		else *blast -= 3;
	}
}

void setup()
{
  Serial.begin(115200);
  Serial.println("travel yet?");

  strip.begin();
  strip.show();

//  no! we neem full brightness to make fading look good. Handle intensity by setting the colour dimmer 
//  strip.setBrightness(191);
}

unsigned char chase(unsigned char start, unsigned char end, unsigned char traveller, bool dir)
{
  traveller++;
    if (traveller >= end - start)
      traveller = 0;

    int colour = strip.Color(255, 0, 0);

    int pix = dir ? traveller + start : end - traveller;

// sorry, did you mean red?
    strip.setPixelColor(pix, 0xff0000);

    return traveller;
}

void loop()
{
  unsigned long now = millis();

  if (now - lastTime < FRAME)
    return;

  lastTime = now;
  if (++bigTick >= STEP) {

    traveller1 = chase(start1, end1, traveller1, direction1);
    traveller2 = chase(start2, end2, traveller2, direction2);
 
    bigTick = 0;
  }

  strip.show();

  ageLEDs();

  if (now - passTime1 < pass1) {
//    return;                    You can't return here without missing the other update!
  }
  else {
    passTime1 = now;
    direction1 = !direction1;
  }

  if (now - passTime2 < pass2) {
//    return;
  }
  else {
    passTime2 = now;
    direction2 = !direction2;
  }
}

Of course I did:

I am not sure, TBH, why it goes bad after awhile. When I have recharged, I will try my own advice and also look for something dumb we both aren't seeing.

I think there is value in getting this two-traveller to work, literal and flawed aesthetically as it may be.

But when there are more, or they need to pass through each other &c., it will be time to stop progress on the project and take another big bite of C/C++.

BTW if you had't noticed, you can concentrate on one traveller first by simply commenting out the second call to chase().

a7

Oh dear, I try my own advice and will need sleep to make sense of it. Sry sry sry..

I got as far as realizing that instead of now, I wanted to have a counter of bigTicks, but this is just as silly as the original reversing logic.

Reverse when you hit the end point. Unfortunately, this means messing up the common code in chase().

Maybe time to jump ahead with the language features you haven't absorbed…

a7

Ok, so here's a fish.

With the introduction of just one concept you haven't yet availed yourself of in your coding, here are two travellers happily, um, travelling.

Whenever you see variable names distinct only because you've added a '1' or '2' to the name, it is time to think about arrays. See your favorite learning source and dive in a bit.

This code is still fairly crude. It uses parallel arrays to house all the information needed to handle a traveller. There's an array of start positions, an array of directions and so forth.

The chase() function just works with all the values in those arrays at a given index. It fully achieves the goal of eliminating duplicate (triplicate, &c.) code.

I say still kinda crude because parallel arrays went out of style more than 50 years ago.

Nevertheless, any modern expression will, at the low level, be doing something similar - there's just prettier ways of doing it these days.

I will always argue that it is worthwhile getting to that point with what some might consider a detour like this.

You can see it will get more unwieldy at some point, like if you wanted to add custom colour or speed or whatever... then it is time for another language feature... but now, adding a third traveller should be easy.

//#include <Arduino.h>
# include <Adafruit_NeoPixel.h>
# define LED_COUNT 24 // set to test in wokwi
# define LED_PIN 8
// Declare our NeoPixel strip object:

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

# define NTRAVELLERS  2

int start[NTRAVELLERS] = {1, 15};       // start points
int end[NTRAVELLERS] = {10, 20};        // end points
char direction[NTRAVELLERS] = {1, 1};   // start going forward
unsigned char traveller[NTRAVELLERS];    // current index of moving LED as ring index

unsigned long lastTime;  // regulate loop to frame rate

# define FRAME 14 // frame time in ms
# define STEP 8   // frames per step
# define FADE 9   // fade amount per frame

unsigned char bigTick; // count STEP to action
unsigned char fullPass;

// reduce all RGB of all LEDs just a little bit
void ageLEDs()
{
	unsigned char *blast = strip.getPixels();

	for (int ii = 0; ii < (LED_COUNT * 3); ii++, blast++) {
		if (*blast < 3) *blast = 0;
		else *blast -= 3;
	}
}

void setup()
{
  Serial.begin(115200);
  Serial.println("travel yet?");

  strip.begin();
  strip.show();
}

void chase(unsigned char theTraveller)
{
  traveller[theTraveller] += direction[theTraveller];

  if (traveller[theTraveller] >= end[theTraveller]) direction[theTraveller] = -1;
  if (traveller[theTraveller] <= start[theTraveller]) direction[theTraveller] = 1;

    strip.setPixelColor(traveller[theTraveller], 0xff0000);
}

void loop()
{
  unsigned long now = millis();

  if (now - lastTime < FRAME)
    return;

  lastTime = now;
  if (++bigTick >= STEP) {

    chase(0);   // advance each traveller
    chase(1);
 
    bigTick = 0;
  }

  strip.show();

  ageLEDs();
}

I could lard it up with endless commentary, but it is a simple enough program - just put your finger on the code and see how it works. There are no tricks.

HTH

a7

first, THANKS, I really appreciate your time and education style.

But it looks like you got out over your skis a little bit.

Ha! Well yes, but for correctness, 'board' [could polarise things!] and been 'off piste' a long while ago, but I know I learn when I have a practical use for something, it's a hard path, but works for me. Again thanks for your helpful instruction...

I made a 24 pixel ring, removed your brightness call and fixed the logic at the end of you loop () which appears to handle the reversal

D'oh... didn't look there, hardware as code, beautiful

I say still kinda crude because parallel arrays went out of style more than 50 years ago.

easy with the agest jokes :face_with_raised_eyebrow: I'm not convinced, having been -1 month old back then, this is an entirely factual statement :thinking: but I get the sentiment. I am now very interested in what is now a better and prettier way to achieve this.

it is time to think about arrays

I'm ok-ish with arrays, it was my go-to for this and then I got caught up with ring buffers and my ageing brain wasn't (clearly) thinking laterally enough to get to this far more elegant solution you have proposed. Although... if the ring was physically positioned so a traveller needed to start at 20 and end at 4 ...

You can see it will get more unwieldy at some point, like if you wanted to add custom colour or speed or whatever

So yes , custom colour is a need. I was thinking I can just pass that in as a variable when calling chase(n) either pass the r,g,b and calculate in the chase routine.

I feel I need to share the context, I was avoiding this initially so the post was to the point... I'll be brief.
Big context, I love a smart house but I don't like a) passing all data to a 3rd party and b) living in an old house; modern devices look awful IMO again period features.

So I have been on a journey to address both.

Context for this and a subsequent write up I plan is Pizza... as most fun things revolve around food and people... that's the hook. I built a masonry oven, based on Alan Scotts design - it's loaded with 6 thermocouples, which are pushing values into homeassistant. I needed a suitable dial to show the temperatures of the oven. As this is a masonry oven (you can remove the fire, close the door to bake, e.g. bread etc.) I need to monitor the temperature decay for different tasks. Knowing when best to re fire or cook, slow roast etc.

So the dial (getting to the neopixel ring) is a 6" steam pressure gauge, with the guts ripped out. I have a switecX27.168 stepper driving the needle, via a quad driver chip; there is an I2C OLED display for detailed info and then the neopixel ring. The dial face will use two 'travellers' to represent the top of oven temp and the hearth, respectively. Colour will be used to represent various temperature stages and the travellers used to indicate temp rising, temp dropping, and temp stable. Four other pixels are used, 1 to indicate if the dial scale should be divided by 10, i.e. if the temp the needle is showing is under 70C then the dial is now 0-70 rather than 0-700 the other three are cooking zones, low, moderate and pizza hot. Sorry to anyone reading that extra text that has zero to do with a code answer... but hopefully explains the direction my questions keep heading.

@alto777 thanks again - I'll digest and attempt to retain.. cheers j

Possibly an array of structs or even an array of objects if you want to be more formal

@UKHeliBob - thanks, so would I be correct in saying that the array of objects would be the pixels e.g. {3,4,5,6,7,8,9} ? I would be doing is managing the index to iterate through the array.

(ironically, I tried this 1st before posting, however then I was poking in the dark, now I'm more enlightened) ... in which case the roll around past 23 is solved as the array as in that case would be {21,22,23,0,1,2} but the index count still behaves the same way??

Should I be thinking of using the actual pixel numbers or should I be storing the location in mem of the pixel value in the array?

e.g. In @alto777 agedLED() function, .getPixels gets the mem location of the RAM buffer... so the fade element is adjusting the pixel value via pointer...

@cl0udb0y there you go again! :wink:

If you add color, do not add an argument to the chase() function. This is totally missing the point of the parallel arrays. Add

unsigned long color[NTRAVELLERS] = {0xff00000, 0x0000ff};

for example, to get one red and one blue. This will keep things moving along the right direction, which is of course

At this point I would just tell you to stop programming, and work your way through chapters 5 and 6 of the bible "The C Programming Language" by K&R, Kernighan and Ritchie.

Or "arrays" and "structs" from your preferred source for learning.

Going by imitation and intuition works OK, but it leaves too many ways for you to get off track. Like adding arguments to functions specifically designed to... not need any.

When you see your structure at the end, you will see my point.

As for the 20 - 4 traveller, this can be handled in many ways. The current traveller uses physical pixel numbers, it would be easy to change that so a traveller went from 0 to N-1 its length, and the use that number as an index into an array that mapped it to a physical pixel. You have a good start on that.

Software is always easier (for some) but this is also a case where thinking ahead might mean you wouldn't have to be worrying about 20 - 3 being the range… I might just grab the stupid ring and reposition it to better advantage.

@UKHeliBob's array of objects, or structs, is an entirely separate matter. What we end up with here is travellers as objects. You'd have two, in this case, each of which would be the home for a complete set of variables that describe it. Again, you will see. Again, take a breather and learn up on structs.

As for my 50 year comment being ageist, not true! And perhaps not strictly accurate, but C has been around since the early 1970s, and from the beginning it has had the struct feature which neatly rolls up everything that goes into (or comes out of) using parallel arrays.

I just wanted to take a small step first. stucts are dead simple conceptually, but using them introduces some additional syntax that I thought might wait while we get some success doing it the hard(er) way.

You could accept as a challenge reworking the code from my #16 to use structs. Add no features. Change no behaviour. Tinker with no logic. Just use a structure struct to make the travellers.

a7