2

I'm remotely connecting my phone to an arduino MKR1000 via wifi, and sending data via HTTP GET (so to switch on the debug LED, I just need to navigate to http://192.168.1.1/debug/H, for example). Now, I want to be able to remotely send animations to an RGBW led strip.

Ideally, I would have a simple keyframe based protocol such as: http://192.168.1.1/anim/(keytime in ms):R,G,B,W+(keytimeinms) etc...

So for example, to animate from white to red in 500ms, I would navigate to: http://192.168.1.1/anim/0:255,255,255,255+500:255,0,0,0

Is this the best way to remotely send animations to my arduino? How should I implement this on the arduino side in the most ressource-efficient way possible? I don't even know where to start :/ For the moment, on the arduino side, I've got a keyframe class and an array of those keyframes representing the animation.

closed as too broad by jfpoilpret, KIIV, Ignacio Vazquez-Abrams, Mattia, Paul Oct 30 '16 at 18:27

Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. Avoid asking multiple distinct questions at once. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    Start with something. Worry about making it efficient after you've figured out how to make it at all. – Ignacio Vazquez-Abrams Oct 30 '16 at 16:46
  • I'm actually working on such a thing (github.com/Paul-Ver/OpenLedstrip). Though it's far from finished and may focus on other points of such a LED driver, but you can check out the HTTP message handling. – Paul Oct 30 '16 at 17:23
  • I'm also using "HUE" instead of "RGB", which makes it easier for a user to select a color (with a slider, going through all hue's). I believe that will make animating a fade much easier. You simply increase/decrease the hue with set steps over time, until you reach the set hue. And same can be done for the brightness. – Paul Oct 30 '16 at 17:37
1

Ignacio's comment, “Start with something. Worry about making it efficient after you've figured out how to make it at all”, is good advice.

Note, to make the transition from one keyframe to another more efficient, use a variant of the Bresenham algorithm. This allows use of fast integer arithmetic and is intrinsically accurate. Instead of error accumulating and getting steadily worse (as will happen if you add constant increments), the Bresenham algorithm tracks error and minimizes it. See, for example, “Simplified Bresenham's line algorithm: What does it exactly do?”.


This edit addresses Paul's question about accumulating error:

I've added a note to my answer, in which I try to battle the accumulation of error, could you check if my assumption (that the last implementation doesn't accumulate the error) is right?

There can be several sources of error, including accumulation of round-off error; arithmetic overflow errors; time-base errors; and silly errors [it's always easier to poke holes in other people's code!].

To illustrate those error sources and a possible solution, consider the following code snippet:

 0   unsigned long int startTime = millis();
 1.  unsigned int totalTime = 3000, nSteps = 100;
 2.  int msPerStep = round(totalTime/nSteps);
 3.  byte startRed = 100, startGreen = 28, startBlue= 50;
 4.  int deltaRed   = endRed   - startRed;
 5.  int deltaGreen = endGreen - startGreen;
 6.  int deltaBlue  = endBlue  - startBlue;
 7.  byte nowRed=startRed, nowGreen=startGreen, nowBlue=startBlue;
 8.  for(int step= 0; step < nSteps; step++){
 9.     analogWrite(RPin, nowRed   += deltaRed/step);
10.     analogWrite(GPin, nowGreen += deltaGreen/nSteps);
11.     nowBlue = startBlue + (deltaBlue/amountOfSteps)*step;
12.     analogWrite(BPin, nowBlue);
13.     long int elapsed = millis()-startTime;
14.     analogWrite(RPin, startRed + (deltaRed*elapsed)/totalTime);
15.     delay(msPerStep);
16.  }

First, consider currentRed += deltaRed/step expression in line 9. In the first loop pass it adds an undefined quantity, deltaRed/0, to nowRed; in the next, it adds deltaRed/1 to nowRed; then deltaRed/2; and so forth.

Line 10 corrects the silly error of dividing by step instead of nSteps. But there can be significant accumulation of round-off error. For example, if deltaGreen is 199 and nSteps is 100, it repeatedly adds 1 instead of 1.99, and ends up with the Green level 99 counts short of being correct.

Line 11 attempts to compensate for that problem by adding a step-proportional amount of deltaBlue to startBlue at each step. However, due to the parenthesized order of arithmetic it has exactly the same round-off error problem as line 10 – it adds exactly one unit per step for my example numbers. To try to avoid this problem, one might write (deltaBlue*step)/amountOfSteps in place of (deltaBlue/amountOfSteps)*step. This will do accurate arithmetic except can easily overflow an int so should perhaps be written instead as (deltaBlue*(long int)step)/amountOfSteps.

Even if line 11 is rewritten to avoid truncation, round-off, and overflow errors, it still is subject to time-base errors. Like the color numbers, the delay() parameter msPerStep is subject to accumulation of round-off errors. During the for loop, several hundred milliseconds of error could accumulate, leading to mismatched or jittery color levels. To avoid that error, instead measure elapsed time (as in line 13) and make color levels proportional to elapsed time (as in line 14). In addition, one probably should change the for loop to a do { ... } while (elapsed < totalTime) loop, ie, let the loop run until the right time has passed, rather than some arbitrary number of passes.

  • Nice note on the "Bresenham algorithm", I also encountered thought of that when writing my answer. But I think that for LED lighting, it wouldn't really be that visible. (Unless it just rolls over 255 and becomes 0.1, then it's about off instead of fully on ;D) – Paul Oct 30 '16 at 18:31
  • I've added a note to my answer, in which I try to battle the accumulation of error, could you check if my assumption (that the last implementation doesn't accumulate the error) is right? EDIT: I'm not even sure if it battles the problem (and is probably inefficient) but it seems logical. – Paul Oct 30 '16 at 18:43
  • @Paul, as I couldn't fit a reply into a comment, I've edited my answer with comments on that question. Let me know if you see any errors in the comments. Thanks! – James Waldby - jwpat7 Oct 30 '16 at 20:01
  • Very good explanation, indeed, if I do the multiplication after the dividing, it will still accumulate the error, missed that one. But nice fix you have there ;) but it indeed is rather easy to overflow, should be tested and specced well, or add some checks. – Paul Oct 31 '16 at 7:13
  • 1
    @Paul, for this particular color animation, an error of 100 in 5000 might not matter. But more generally, for sound or picture sequences, a tenth of a second is glaringly obvious. Even a hundredth of a second gap is pretty obvious and makes a click in a sound sequence. – James Waldby - jwpat7 Oct 31 '16 at 8:54
0

I'm not sure what you have working or what you haven't. I'm also not sure what you've tried and which problems occured. However, I'd like to give you atleast a basic answer before I vote for: "Too broad".

It's actually quite simple (but you can make it difficult).

Step 1

Imagine the effect, what are you actually trying to achieve? Fade out the current color and fade in a new one? Or are you trying to shift the color from one to another?

Step 2

Find resources online. You're likely not the first person to have tried this ;D https://www.arduino.cc/en/Tutorial/ColorCrossfader

Step 3

Iteratively and rationally develop your project. Know what you have and what you need.

You've got these parameters:

  • Current R/G/B values.
  • R/G/B values you wish to shift/fade to.
  • Duration of the fading effect (total time of the animation, total amount of frames for the animation)

Your goal:

Is making an "animation". Changing the colors over time, as an fading effect.

We will simplyfy it by starting with only the R (red) channel, we can add the rest later.

Let's say we want to fade from 100 to 255 in 3 seconds. We're going to have 155 different levels of brightness over 3 seconds. Which is roughly 20ms per step.

for(currentRed; currentRed < setRed; currentRed++){
   analogWrite(redPin,currentRed);
   delay(msPerStep);
}

But that won't really work for multiple channels... Just add another channel and find your way out that challenge. (I added blue, but you could add up to x amount with this code I believe)

unsigned char currentRed = 100;
unsigned char currentBlue = 50;
unsigned char setRed  = 255;
unsigned char setBlue = 255;
int differenceRed  = setRed  - currentRed;
int differenceBlue = setBlue - currentBlue;
unsigned int totalTime = 3000; //3sec (in ms)
unsigned int amountOfSteps = 100;
int msPerStep = round(totalTime/amountOfSteps);

for(unsigned int step= 0; step < amountOfSteps; step++){
   analogWrite(redPin, currentRed  += differenceRed/step);//We could optionally calculate the difference per step beforehand, to avoid having doing dividing in a loop (may be a tad inefficient this way).
   analogWrite(bluePin,currentBlue += differenceBlue/step);//But well, in the delay you're also just wasting CPU, so who cares? :)
   delay(msPerStep);
}
currentRed = setRed;//Compensate for rounding step values
currentBlue=setBlue;//You may want update the analog pins, though I presume it would be barely visible to a person.

You could get rid of the blocking delay, but well, that's beyond the scope of this answer.

I haven't tested the code, nor the algorithm extensively. You may check what happens if the setpoint is lower as the current value.

So now you can test the algorithm, finetune/adapt it to your liking and make a function out of it :)



As noted by jwpat7, combining floating point math and integers on Arduino will have quite an error in it. And if you keep applying the same error each step, it will accumulate.

I'm not sure how significant it will be for LED applications, you usually can't see the difference between a few "levels" of brightness.

I've made my own implementation of which I think may battle this problem:

unsigned char previousRed = 100;
unsigned char previousBlue= 50;
unsigned char currentRed = previousRed;
unsigned char currentBlue = previousBlue;
unsigned char setRed  = 255;
unsigned char setBlue = 255;
unsigned int totalTime = 3000; //3sec (in ms)
unsigned int amountOfSteps = 100;
int msPerStep = round(totalTime/amountOfSteps);

for(unsigned int step= 0; step < amountOfSteps; step++){
    currentRed = previousRed + ((setRed-previousRed)/amountOfSteps)*step;
    currentBlue = previousBlue + ((setBlue-previousBlue)/amountOfSteps)*step;
    analogWrite(redPin, currentRed);
    analogWrite(bluePin,currentBlue);
    delay(msPerStep);//If the algorithm is inefficient, you COULD compensate for it by lowering the msPerStep.
}
currentRed=setRed;
currentBlue=setBlue;

I believe that, by completely recalculating it every step, the error won't accumulate.

This, however, may be a lot more inefficient than what jwpat7 describes. But atleast it's easy to understand. Since you're throwing most of the cycles away by waiting for the next frame and can barely see the difference between "100 and 102" intensity from PWM, I think it's a viabale option.

Not the answer you're looking for? Browse other questions tagged or ask your own question.