Multitasking

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
Rcayot
 
Posts: 321
Joined: Sat Feb 08, 2020 6:48 pm

Multitasking

Post by Rcayot »

There is a great series by Bill Earl called multitasking on arduino. Is there something similar for circuitpython?

I have done a bit of beginner arduino programming, and would like to move to CP because of all of the great products and supporting libraries at Adafruit. I have yet to find, however, a good guide to using classes for multitasking using CP. and the new years eve drop ball example does not help me, it is more of a state machine example.

Also, any other resources on that subject would be helpful. Most online python resources do not address aspects of physical computing.

Thanks,
Roger Ayotte

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

the new years eve drop ball example does not help me, it is more of a state machine example.
State machines are the key to multitasking in a purely single-threaded environment. The state machine classes in Dave's ball-drop tutorial are structured a little differently from the state machines in my Multitasking the Arduino series. But overall effect is similar. The main processing loop just needs to call the update function.

To make it multitask, you create multiple state machines and call update on all of them.

User avatar
tannewt
 
Posts: 3304
Joined: Thu Oct 06, 2016 8:48 pm

Re: Multitasking

Post by tannewt »

Hi Roger,
CircuitPython doesn't support any of Python's concurrency mechanics. There is a long issue about it here if you are interested: https://github.com/adafruit/circuitpython/issues/1380
~Scott

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

Hi all

I hope someone can help.
I'm an artist working on a project that requires two NeoPixel strips running two animations simultaneously.
Bill's Multitasking sketch is perfect and I've been able to change Rings to Strips, remove the button functions and play with the patterns included.
However, I need a new pattern integrated into the code and have no idea how to make it work.
I'm not a coder and am struggling.

Below is the pulse code I want to assign to one of the strips.

Can anyone help or point me in the right direction?

It would be very much appreciated

Thanks Guys

Andy Wilx

#include <Adafruit_NeoPixel.h>
#define PIN 6
#define NUMPIXELS 12
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int dir = 1;
int bright = 50;

void setup() {

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

}

void loop() {
uint32_t blueFade = strip.ColorHSV(43650,255,bright);
strip.fill(blueFade);
strip.show();
bright = bright + dir;
if(bright > 100 || bright < 10) dir = dir * -1;
delay(25);
}

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

You can accomplish that effect using the Fade pattern from the guide: https://learn.adafruit.com/multi-taskin ... rt-3/fader
Set color1 to be your brightest blue color and color2 to be the dimmest and use an interval of 25.

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

Thanks for coming back to me.

I have done that and it obviously works as a fade but not a pulse.
It's not quite the same effect as it fades off, then pops on.
This is more subtle as the end point isn't quite off so looks like a pulse not a fade.

Also how do I keep it to one colour?
I'm after a single colour heartbeat.

I guess where I struggle with this type of code, is the lack of tangible variables. A string of words rather than percentages and values.
I'll clearly never be a coder. ;o)

Any help will be very much appreciated.

Andy

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

This is more subtle as the end point isn't quite off
The fade lets you set the end point to any arbitrary color. color2 does not need to be black. If you want more of a 'breathing' effect, just reverse the direction on completion.
Also how do I keep it to one colour?
You specify the start color (color1) and the end color (color2). They can be different shades of the same color.

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

Thanks again for the reply.

I've got it to fix to one color, (simply turn off the random update).

But this isn't the same animation. This is a fade not a pulse.

Fade: 3-2-1-off-3-2-1-off
Pulse: 3-2-1-off-1-2-3-2-1-off-1-2-3

See my original code.
Apologies if I'm not able to see it in your code.
I'm not a coder.

I'm so close I can smell it but my lack of knowledge is frustrating.

Help would be very welcome.

Andy

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

It's hard to offer specific advice since you haven't posted your code.
I've got it to fix to one color, (simply turn off the random update).
There is nothing random in the fade pattern. It does what you program it to do.
Fade: 3-2-1-off-3-2-1-off
Pulse: 3-2-1-off-1-2-3-2-1-off-1-2-3
AS mentioned in my previous post, If you want a breathing effect, simply reverse the fade on completion.
https://learn.adafruit.com/multi-taskin ... -1167790-9

Code: Select all

if (strip.Direction == FORWARD)
{
   strip.Direction = REVERSE;
}
else
{
  strip.Direction = FORWARD;
}

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

[The extension ino has been deactivated and can no longer be displayed.]

OK, I didn't want to do this but please find attached my code, (well, your code really with my clumsy hacks).
I'm after:
Strip 1 Rainbow Cycle (I'll want to add audio responsive code next ;o)
Strip 2 Pulsing heart beat in one colour.

P.S.
I didn't mean your code was random, I meant removing the 'Random' Call Back seemed to work for me.
"Stick.Color1 = Stick.Wheel(random(255));"

Many Many thanks for your help.

Andy

Code: Select all

#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
    
    // 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 RAINBOW_CYCLE:
                    RainbowCycleUpdate();
                    break;
                case THEATER_CHASE:
                    TheaterChaseUpdate();
                    break;
                case COLOR_WIPE:
                    ColorWipeUpdate();
                    break;
                case SCANNER:
                    ScannerUpdate();
                    break;
                case FADE:
                    FadeUpdate();
                    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 RainbowCycle
    void RainbowCycle(uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = RAINBOW_CYCLE;
        Interval = interval;
        TotalSteps = 255;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Rainbow Cycle Pattern
    void RainbowCycleUpdate()
    {
        for(int i=0; i< numPixels(); i++)
        {
            setPixelColor(i, Wheel(((i * 256 / numPixels()) + Index) & 255));
        }
        show();
        Increment();
    }

    // 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();
    }

    // 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;
    }
    
    // Update the Color Wipe Pattern
    void ColorWipeUpdate()
    {
        setPixelColor(Index, Color1);
        show();
        Increment();
    }
    
    // Initialize for a SCANNNER
    void Scanner(uint32_t color1, uint8_t interval)
    {
        ActivePattern = SCANNER;
        Interval = interval;
        TotalSteps = (numPixels() - 1) * 2;
        Color1 = color1;
        Index = 0;
    }

    // Update the Scanner Pattern
    void ScannerUpdate()
    { 
        for (int i = 0; i < numPixels(); i++)
        {
            if (i == Index)  // Scan Pixel to the right
            {
                 setPixelColor(i, Color1);
            }
            else if (i == TotalSteps - Index) // Scan Pixel to the left
            {
                 setPixelColor(i, Color1);
            }
            else // Fading tail
            {
                 setPixelColor(i, DimColor(getPixelColor(i)));
            }
        }
        show();
        Increment();
    }
    
    // Initialize for a Fade
    void Fade(uint32_t color1, uint32_t color2, uint16_t steps, uint8_t interval, direction dir = FORWARD)
    {
        ActivePattern = FADE;
        Interval = interval;
        TotalSteps = steps;
        Color1 = color1;
        Color2 = color2;
        Index = 0;
        Direction = dir;
    }
    
    // Update the Fade Pattern
    void FadeUpdate()
    {
        // Calculate linear interpolation between Color1 and Color2
        // Optimise order of operations to minimize truncation error
       uint8_t red = ((Red(Color1) * (TotalSteps - Index)) + (Red(Color2) * Index)) / TotalSteps;
        uint8_t green = ((Green(Color1) * (TotalSteps - Index)) + (Green(Color2) * Index)) / TotalSteps;
       uint8_t blue = ((Blue(Color1) * (TotalSteps - Index)) + (Blue(Color2) * Index)) / TotalSteps;
        
       ColorSet(Color(red, green, blue));
       show();
       Increment();
    }
   
    // Calculate 50% dimmed version of a color (used by ScannerUpdate)
    uint32_t DimColor(uint32_t color)
    {
        // Shift R, G and B components one bit to the right
        uint32_t dimColor = Color(Red(color) >> 1, Green(color) >> 1, Blue(color) >> 1);
        return dimColor;
    }

    // Set all pixels to a color (synchronously)
    void ColorSet(uint32_t color)
    {
        for (int i = 0; i < numPixels(); i++)
        {
            setPixelColor(i, color);
        }
        show();
    }

    // Returns the Red component of a 32-bit color
    uint8_t Red(uint32_t color)
    {
        return (color >> 16) & 0xFF;
    }

    // Returns the Green component of a 32-bit color
    uint8_t Green(uint32_t color)
    {
        return (color >> 8) & 0xFF;
    }

    // Returns the Blue component of a 32-bit color
    uint8_t Blue(uint32_t color)
    {
        return color & 0xFF;
    }
    
    // Input a value 0 to 255 to get a color value.
    // The colours are a transition r - g - b - back to r.
    uint32_t Wheel(byte WheelPos)
    {
        WheelPos = 255 - WheelPos;
        if(WheelPos < 85)
        {
            return Color(255 - WheelPos * 3, 0, WheelPos * 3);
        }
        else if(WheelPos < 170)
        {
            WheelPos -= 85;
            return Color(0, WheelPos * 3, 255 - WheelPos * 3);
        }
        else
        {
            WheelPos -= 170;
            return Color(WheelPos * 3, 255 - WheelPos * 3, 0);
        }
    }
};

void Strip1Complete();
void Strip2Complete();

NeoPatterns Strip1(12, 5, NEO_GRB + NEO_KHZ800,  &Strip1Complete);
NeoPatterns Strip2(12, 6, NEO_GRB + NEO_KHZ800,  &Strip2Complete);

void setup()
{

    Strip1.begin();
    Strip2.begin();
    
    // Kick off a pattern
    
    //THEATERCHASE
    //Strip2.TheaterChase(Strip1.Color(255,255,0), Strip2.Color(0,0,50), 100);

    //RAINBOWCYCLE
    Strip1.RainbowCycle(10);
    Strip1.Color1 = Strip1.Color1;

    //SCANNER
    //Strip2.Scanner(Strip2.Color(50,0,255), 255);

    //FADE
    Strip2.Scanner(Strip2.Color(50,0,255), 255);
    Strip2.ActivePattern = FADE;
        Strip2.Interval = 100;
}

// Main loop
void loop()
{
    // Update the Strips.
    Strip1.Update();
    Strip2.Update();    
    
}


// Strip1 Completion Callback
void Strip1Complete()
{
 
}

//Strip2 Completion Callback
void Strip2Complete()
{
  if (Strip2.Direction == FORWARD)
{
   Strip2.Direction = REVERSE;
}
else
{
  Strip2.Direction = FORWARD;
}
}
Last edited by adafruit_support_bill on Wed Aug 12, 2020 8:46 am, edited 1 time in total.
Reason: added code in-line

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

Code: Select all

    //FADE
    Strip2.Scanner(Strip2.Color(50,0,255), 255);
    Strip2.ActivePattern = FADE;
        Strip2.Interval = 100;
You are calling the initialization for Scanner which takes just one color and an interval. You should be calling the initialization for the Fader which lets you configure both colors, as well as the number of steps, direction and interval for your fade. https://learn.adafruit.com/multi-taskin ... -1167113-2

Code: Select all

    //FADE - bright blue to dim blue - 100 steps - 10 ms per step
    Strip2.Fade(Strip2.Color(50,0,255), Strip2.Color(0,0,25), 100, 10, FORWARD);

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

That's great thank you.

And does the:

if (Strip2.Direction == FORWARD)
{
Strip2.Direction = REVERSE;
}
else
{
Strip2.Direction = FORWARD;
}

Code go into the Completion Callback?
I've put it there and then just cycles in reverse.

I had a fiddle with defining the reverse in the Callback, which I can see is ugly and not good code but it worked properly for one cycle. It then just loops in reverse.

//Strip2 Completion Callback
void Strip2Complete()
{
if (Strip2.Color(0,0,25))
{
// Strip2.Direction = REVERSE;
Strip2.Fade(Strip2.Color(0,0,25), Strip2.Color(0,0,255), 100, 10);
}
else
{
Strip2.Direction = FORWARD;
}
}

I'm assuming I'm not defining what FORWARD is in the main loop?
Attachments

[The extension ino has been deactivated and can no longer be displayed.]


User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

This should work:

Code: Select all

//Strip2 Completion Callback
void Strip2Complete()
{
	if (Strip2.Direction == FORWARD)
	{
		Strip2.Direction = REVERSE;
	}
	else
	{
		Strip2.Direction = FORWARD;
	}
}

User avatar
AndyWilx
 
Posts: 8
Joined: Mon Aug 10, 2020 8:12 am

Re: Multitasking

Post by AndyWilx »

I tried that first and it doesn't.
this is the result:

3-2-1-flicker-1-2-3
1-2-3
1-2
1-2
1-2-3
1-2-3
1-2
1-2

If I add
void Strip2Complete()
{
if (Strip2.Direction == FORWARD)
{
Strip2.Direction = REVERSE;
Strip2.Fade(Strip2.Color(0,0,25), Strip2.Color(0,0,255), 100, 10);

}
else
{
Strip2.Direction = FORWARD;
Strip2.Fade(Strip2.Color(0,0,255), Strip2.Color(0,0,25), 100, 10);

}
}

I get:
3-2-1-2-3
1-2-3
1-2-3
1-2-3

Frustratingly close.

User avatar
adafruit_support_bill
 
Posts: 88093
Joined: Sat Feb 07, 2009 10:11 am

Re: Multitasking

Post by adafruit_support_bill »

Try this:

Code: Select all

//Strip2 Completion Callback
void Strip2Complete()
{
   if (Strip2.Direction == FORWARD)
   {
      Strip2.Direction = REVERSE;
	  Strip2.Index = Strip2.TotalSteps-1;
   }
   else
   {
      Strip2.Direction = FORWARD;
	  Strip2.Index = 0;
   }
}

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”