Neopixels, Classes, constructors and functions

In case of TLDR, we’re going to Tarantino this and start at the end.

My question is: Where is the actual value associated with each color? e.g. “16711935”, or “0xFF00FF”?

Now the relevant info:

The following code is from Overview | Multi-tasking the Arduino - Part 3 | Adafruit Learning System

A class definition is given:

#include <Adafruit_NeoPixel.h>

// Pattern types supported:
enum  pattern { NONE, RAINBOW_CYCLE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum  direction { FORWARD, REVERSE };

// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
    public:

    // Member Variables:  
    pattern  ActivePattern;  // which pattern is running
    direction Direction;     // direction to run the pattern
    
    unsigned long Interval;   // milliseconds between updates
    unsigned long lastUpdate; // last update of position
    
    uint32_t Color1, Color2;  // What colors are in use
    uint16_t TotalSteps;  // total number of steps in the pattern
    uint16_t Index;  // current step within the pattern
    
    void (*OnComplete)();  // Callback on completion of pattern

Then later a function is created using the class definition, but then then Color1 is only defined as “color”? Am I reading that right?

// Initialize for a ColorWipe
    void ColorWipe(uint32_t color, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = COLOR_WIPE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color;
        Index = 0;
        Direction = dir;
    }

Now the background if you care to know:

I am beginning a project using some cool white neopixels from adafruit. To try and make the code behave the way I want I am moving away from delays and trying to use a State Machine approach. Using this guide I think I get the jist of how classes, constructors and functions work together:

But when the author moves into part 2 he gets into interrupts and I start to lose him. No big deal, I can come back to that, I want to start with the part 1 stuff first.

But pressing on to part 3, where it is applied to neopixels, I lose him completely as far as how the variables, constructors and functions relate back to the original class definition.

Maybe it’s not the code itself, but the neopixel angle that is confusing me. Between the pixels and the code I’m about to go blind from staring at these things anyway, they are burning out my retinas.

My question is: Where is the actual value associated with each color? e.g. "16711935", or "0xFF00FF"?

When you call the function you pass a value from your code to the function, let's call it myColor (must be of type uint32_t) ex: 0xFF00FF i.e. color

Color1 (in the fuction) is then assigned this value. Color1 = color;

to go blind from staring at these things anyway, they are burning out my retinas.

Diffuse the light with white paper.

LarryD: When you call the function you pass a value from your code to the function, let's call it myColor (must be of type uint32_t) ex: 0xFF00FF i.e. color

Color1 (in the fuction) is then assigned this value. Color1 = color;

So within the loop for example I call the ColorWipe function and pass it the variables for the color, the interval and the direction?

So my code might read something like:

ColorWipe(0xFF00FF, 20, FORWARD)

Is that correct?

go blind from staring at these things anyway, they are burning out my retinas.

Turn down the intensity of each color. You have 0-255 as levels for brightness control, use a smaller number.

CrossRoads: Turn down the intensity of each color. You have 0-255 as levels for brightness control, use a smaller number.

Yeah, I know that will be the plan eventually, but as I run code written by others I am always looking at the things to see if they are dimming, flashing, blinking, etc. I find myself staring into them like the where's the beef lady. "Is it dimming yet??" No, but I'm blind, thanks for asking. :astonished:

Anyway, I found a piece of smoked glass earlier today that fits over the whole breadboard, so that has helped.

ColorWipe(0xFF00FF, 20, FORWARD); OR you could: ColorWipe(myColor, myTime, FORWARD); // where myColor could be 0xFF00FF and myTime could be 20

.

LarryD: ColorWipe(0xFF00FF, 20, FORWARD); OR you could: ColorWipe(myColor, myTime, FORWARD); // where myColor could be 0xFF00FF and myTime could be 20

.

But then if I used myColor and myTime I would have to further define those within the function, right?

For example:

ColorWipe(myColor, myTime, FORWARD); // where myColor could be 0xFF00FF and myTime could be 20
myColor = 0xFF00FF;
myTime = 20;

Ok, so in trying to understand how this code drives these neopixels, my next question is regarding the COLOR_WIPE function that is actually called in the loop. (I would paste it in, but I exceed the 9000 character limit) Here's the link again:

https://learn.adafruit.com/multi-tasking-the-arduino-part-3?view=all#put-it-all-together-dot-dot-dot

I see in lines 294-296 the parameters are set for the numbers of pixels, etc. That makes sense. But in lines 339-340, the COLOR_WIPE is set as the active pattern. But no other parameters are passed to make that happen. Am I missing something?

This sort of ties into my initial question of "Where does the color come from???" The In the case of this code I can't seem to tell!

This stuff seems so far over my head and it is really discouraging trying to figure it out. It's not enough that I upload it and it works, I want to know why!

bagpiper03: But then if I used myColor and myTime I would have to further define those within the function, right?

For example:

ColorWipe(myColor, myTime, FORWARD); // where myColor could be 0xFF00FF and myTime could be 20
myColor = 0xFF00FF;
myTime = 20;

I think it would do you some good to look at a tutorial on functions in C and C++. You seem to have some fundamental misunderstandings on how this works and it would be too much to try to educate you in a forum post.

myColor and myTime would have to be defined in the calling function, not in the function ColorWipe. myColor would be the thing you are passing to the function to serve as color.

In lines 339-340 you are setting ActivePattern to COLOR_WIPE. If you look, ActivePatter is defined as type pattern which is an enum.

SO really it is saying ActivePattern = 3;

Now it hits the Update function which calls ColorWipeUpdate.

ColorWipeUpdate looks like this:

void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }

and does its stuff with the colors you have already chosen earlier here:

 Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);

It looks like the ColorWipe function is just there for if you want to change those things before running the animation. But as long as Index and Color1 have values, you don't need to.

Delta_G: In lines 339-340 you are setting ActivePattern to COLOR_WIPE. If you look, ActivePatter is defined as type pattern which is an enum.

SO really it is saying ActivePattern = 3;

Now it hits the Update function which calls ColorWipeUpdate.

ColorWipeUpdate looks like this:

void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }

and does its stuff with the colors you have already chosen earlier here:

 Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);

It looks like the ColorWipe function is just there for if you want to change those things before running the animation. But as long as Index and Color1 have values, you don't need to.

It's clear to me that you see it, but unfortunately I still don't. :confused:

I think part of the problem is that I sort of backed into this tutorial by having a project that required neopixels. I also have need to do away with delays in my project and I have been cussed at on these forums before for overusing them. So this is my attempt to get away from that since it will allow me to do many things at once without tying up the processor.

The problem with where I am now is that the adafruit tutorial starts off as a discussion of how to avoid using delays in step one, then how to use timers and reorganize your code, etc in step 2, then in step three a rocket launches for another universe and I'm left scratching my head. I guess it's enough for some people just to plug it in, copy some code and wear it as a necklace but if you have any other use, forget about it because it's totally inscrutable. Somehow we went from flashing LEDs and turning Servos in steps one and two with clear explanations, and now in step three nothing is explained fully to allow for reusing any code.

And don't start me on their "Neopixel Uberguide". That's got a few holes in it as well from a beginner standpoint.

I guess back to my initial question if you have the patience to try and answer it again in a different way:

I still don't see how

Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);

finds its way back to "Color1" or "Index".

And as per your earlier advice to seek out a primer on functions, I have reserved three library books on C/C++ and I look forward to understanding that a little clearer very shortly.

Thanks again for your patience to anyone who cares to help.

bagpiper03: I still don't see how

Ring1.TheaterChase(Ring1.Color(255,255,0), Ring1.Color(0,0,50), 100);

finds its way back to "Color1" or "Index".

OK, here's the TheaterChase function:

  void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }

It sets Interval to the third argument (in our case 100) Color1 to the first argument (in our case Ring1.Color(255,255,0)) and Color2 to the second argument (Ring1.Color(0,0,50)) Index gets set to 0 and Direction gets set to the last argument.

So that sets all those values for the Ring1 object. Then they go and set similar values for the Ring2 object.

LarryD: Diffuse the light with white paper.

The best trick I've seen and used is a section of a glue stick. With a 5mm/3mm hole they are a snug fit and look excellent.

Someone once made a huge 7-segment using glue sticks to make each segment.

I'll give it a try.

Delta_G: OK, here's the TheaterChase function:

  void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }

It sets Interval to the third argument (in our case 100) Color1 to the first argument (in our case Ring1.Color(255,255,0)) and Color2 to the second argument (Ring1.Color(0,0,50)) Index gets set to 0 and Direction gets set to the last argument.

So that sets all those values for the Ring1 object. Then they go and set similar values for the Ring2 object.

Hooray now I think I see it now! At the risk of confusing myself further I'm going to stop asking questions now! I'm going to try and reuse some of this code to do my own thing and see how it goes. I'll post back with results and issues as they arise! Thanks again for helping!

I have made some progress over the last two days. I have stripped the code down to try and get a better handle on it. I have removed most of the random color generating code and replaced it with some fixed colors just to experiment.

My next question is similar to the last one. I am a little foggy (clearly) on the vocab/jargon, but please bear with me.

In line 146, the TheaterChase function is called. It is defined in lines 99-108.

Line 99 seems to say, “If you’re going to call TheaterChase, I need know color1, color2, interval and direction.”

That much I think I understand. The items in lines 100-108 I’m a little foggy on.

I get that in line 101 it defines the ActivePattern as THEATER_CHASE.

But in line 102, it takes the value for interval and assigns it to “Interval”. Why? Can’t the rest of the code just use interval instead of Interval?

Is there a reason this seems superfluous to a novice?

#include <Adafruit_NeoPixel.h>

// Pattern types supported:
enum  pattern { NONE, THEATER_CHASE, COLOR_WIPE, SCANNER, FADE };
// Patern directions supported:
enum  direction { FORWARD, REVERSE };

// NeoPattern Class - derived from the Adafruit_NeoPixel class
class NeoPatterns : public Adafruit_NeoPixel
{
    public:

    // Member Variables:  
    pattern  ActivePattern;  // which pattern is running
    direction Direction;     // direction to run the pattern
    
    unsigned long Interval;   // milliseconds between updates
    unsigned long lastUpdate; // last update of position
    
    uint32_t Color1, Color2;  // What colors are in use
    uint16_t TotalSteps;  // total number of steps in the pattern
    uint16_t Index;  // current step within the pattern
    
    void (*OnComplete)();  // Callback on completion of pattern
    
    // Constructor - calls base-class constructor to initialize strip
    NeoPatterns(uint16_t pixels, uint8_t pin, uint8_t type, void (*callback)())
    :Adafruit_NeoPixel(pixels, pin, type)
    {
        OnComplete = callback;
    }
    
    // Update the pattern
    void Update()
    {
        if((millis() - lastUpdate) > Interval) // time to update
        {
            lastUpdate = millis();
            switch(ActivePattern)
            {
                case THEATER_CHASE:
                    TheaterChaseUpdate();
                    break;
                default:
                    break;
            }
        }
    }
	
    // Increment the Index and reset at the end
    void Increment()
    {
        if (Direction == FORWARD)
        {
           Index++;
           if (Index >= TotalSteps)
            {
                Index = 0;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
        else // Direction == REVERSE
        {
            --Index;
            if (Index <= 0)
            {
                Index = TotalSteps-1;
                if (OnComplete != NULL)
                {
                    OnComplete(); // call the comlpetion callback
                }
            }
        }
    }
    
    // Reverse pattern direction
    void Reverse()
    {
        if (Direction == FORWARD)
        {
            Direction = REVERSE;
            Index = TotalSteps-1;
        }
        else
        {
            Direction = FORWARD;
            Index = 0;
        }
    }
    

    


    // Initialize for a Theater Chase
    void TheaterChase(uint32_t color1, uint32_t color2, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = THEATER_CHASE;
        Interval = interval;
        TotalSteps = numPixels();
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
   }
    
    // Update the Theater Chase Pattern
    void TheaterChaseUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            if ((i + Index) % 3 == 0)
            {
                setPixelColor(i, Color1);
            }
            else
            {
                setPixelColor(i, Color2);
            }
        }
        show();
        Increment();
    }
};

// Define some NeoPatterns for the two rings and the stick
//  as well as some completion routines
NeoPatterns Ring1(3, 6, NEO_GRB + NEO_KHZ800, &Ring1Complete);

// Initialize everything and prepare to start
void setup()
{
  Serial.begin(9600);

   pinMode(8, INPUT_PULLUP);
   pinMode(9, INPUT_PULLUP);
    
    // Initialize all the pixelStrips
    Ring1.begin();

    
    // Kick off a pattern
    Ring1.TheaterChase(Ring1.Color(5,0,0), Ring1.Color(0,0,10), 10);
}

// Main loop
void loop()
{
    // Update the rings.
    Ring1.Update();  
    
    // Switch patterns on a button press:
    if (digitalRead(8) == LOW) // Button #1 pressed
    {
        // Switch Ring1 to FASE pattern
        Ring1.Color(255,255,255);

    }
    else if (digitalRead(9) == LOW) // Button #2 pressed
    {
        // Switch to alternating color wipes on Rings1 and 2
        Ring1.ActivePattern = COLOR_WIPE;
    }
    else // Back to normal operation
    {
        // Restore all pattern parameters to normal values
        Ring1.ActivePattern = THEATER_CHASE;
        Ring1.Interval = 200;
    }    
}

//------------------------------------------------------------
//Completion Routines - get called on completion of a pattern
//------------------------------------------------------------

// Ring1 Completion Callback
void Ring1Complete()
{
    if (digitalRead(9) == LOW)  // Button #2 pressed
    {
        // Alternate color-wipe patterns with Ring2
        Ring1.Color1 = Ring1.Color(255,255,255);
        Ring1.Interval = 20000;
    }
    else  // Retrn to normal
    {
      Ring1.Reverse();
    }
}

Can't the rest of the code just use interval instead of Interval?

Well, the good thing about software, is you can try out your hypothesis. Try using only "interval"

What happens when you try?

.

LarryD: Well, the good thing about software, is you can try out your hypothesis. Try using only "interval"

What happens when you try?

.

Removing line 102 and changing all "Interval" to "interval", it seems to work as it did before. Is that just a fluke or was it indeed unnecessary?

It is my guess this all has to do with programming style.

The author is/was probably use to having a local variable (in this case "Interval") take on what ever the function is called with i.e. "interval". This would allow them to change it (Interval) and still have the original "interval' variable if needed.

Since "Interval" is lost when the function is finished, there is no SRAM used, as there would be if a Global variable was used.