Fading an LED with a negative delta value

Hi Guys I´m struggeling on a fading an LED over time (simplistic code, in the main thing of course I check vAct to be between 0 and 255 and the function to stop after tFade:

unsigned long tStart, tFade, now
int vStart, vEnd;

void fadeLED()
{
   now = millis();
   int vDelta = vEnd - vStart;

   int vAct = vStart + vDelta * ((unsigned long)now - tStart) / tFade; //Struggling here what typecasts to use where

   analogWrite(LEDpin, vAct);
}

void loop()
{
 fadeLED();
}

It works fine for fading towards a bigger value, therefore with a positive vDelta. With a negative one I get an rollover zero. what datatypes to use correctly or what typecasts to apply to make it work both ways?

Your code, as posted, won't even compile.

You are not printing anything in the function, so you are debugging by guessing. That is the worst possible way to debug code.

If you want easy, have a look at FadeLed :)

As I said, this is the simplistic version leaving everything out that does not belong to the question. My code compiles, and I do print values. The question regards how to calculate

int = int + (unsigned long * int(neg. here) / unsigned long) and make it work with a negative value.

The question regards how to calculate

The issue is that you have no proof that the current calculation is wrong. If it is wrong, you have no proof as to how it is wrong.

When you have that, we can talk.

Or, if you don't want easy, you can still peek how I did it ;)

PS It does not compile! At least not without setup(), ';' and a definition of LEDpin... No trouble with an minimal sketch, that is great! But make it a Minimal, Complete, and Verifiable example or MCVE.

struct RGB { int r; int g; int b; };

class RGBLED {
private:
 int _rAct;
 int _gAct;
 int _bAct;
 int _rStart;
 int _gStart;
 int _bStart;
 int _rEnd;
 int _gEnd;
 int _bEnd;
 int _rPin;
 int _gPin;
 int _bPin;
 void _set(RGB color);
 unsigned long _tFadeStart;
 unsigned long _tFade;
 unsigned long _tlast;
 bool _isFading;
public:
 RGBLED(int rPin, int gPin, int bPin);
 void on(RGB color);
 void off();
 void fadeOff(unsigned long fadeTime);
 void fadeTo(RGB color, unsigned long fadeTime);
 void update();
 bool isFading() { return _isFading; }
};

RGBLED::RGBLED(int rPin, int gPin, int bPin) :
 _rPin{ rPin },
 _gPin{ gPin },
 _bPin{ bPin },
 _rAct{ 0 },
 _gAct{ 0 },
 _bAct{ 0 },
 _rStart{ 0 },
 _gStart{ 0 },
 _bStart{ 0 },
 _tlast{ 0 },
 _isFading{false}
{
 pinMode(rPin, OUTPUT);
 pinMode(gPin, OUTPUT);
 pinMode(bPin, OUTPUT);
}

void RGBLED::_set(RGB color)
{
 if (color.r < 0) color.r = 0;
 if (color.g < 0) color.g = 0;
 if (color.b < 0) color.b = 0;
 if (color.r > 255) color.r = 255;
 if (color.g > 255) color.g = 255;
 if (color.b > 255) color.b = 255;

 _rAct = color.r;
 _gAct = color.g;
 _bAct = color.b;

 analogWrite(_rPin, _rAct);
 analogWrite(_gPin, _gAct);
 analogWrite(_bPin, _bAct);
}

void RGBLED::on(RGB color)
{
 this->_set(color);
}

void RGBLED::fadeTo(RGB color, unsigned long fadeTime)
{
 if (fadeTime <= 0)
 {
 this->_set(color);
 return;
 }
 else
 {
 _tFadeStart = millis();
 _tFade = fadeTime;
 _isFading = true;
 _rEnd = color.r;
 _gEnd = color.g;
 _bEnd = color.b;
 _rStart = _rAct;
 _gStart = _gAct;
 _bStart = _bAct;
 }

}

void RGBLED::update()
{
 if(_isFading)
 {
 unsigned long now = millis();
 if (((unsigned long)now - _tFadeStart) > _tFade)
 {
 this->_set((RGB) { _rEnd, _gEnd, _bEnd });
 _isFading = false;
 return;
 }
 if (((unsigned long)now - _tlast) > 200)
 {
 int Dr = _rEnd - _rStart;
 int Dg = _gEnd - _gStart;
 int Db = _bEnd - _bStart;
 unsigned long Dt = now - _tFadeStart;

 int r = _rStart + (Dt * Dr / _tFade);
 if (r < 0) r = 0;
 if (r > 255) r = 255;
 int g = _gStart + (Dg * Dt / _tFade);
 if (g < 0) g = 0;
 if (g > 255) g = 255;
 int b = _bStart + (Db * Dt / _tFade);
 if (b < 0) b = 0;
 if (b > 255) b = 255;
 this->_set((RGB) { r, g, b });
 Serial.println("Time");
 Serial.print(r);
 Serial.print(" = ");
 Serial.print(_rStart);
 Serial.print(" + ( ");
 Serial.print(Dr);
 Serial.print(" * ");
 Serial.print(Dt);
 Serial.print(" / ");
 Serial.print(_tFade);
 Serial.println(" ) ");
 _tlast = now;
 }
 }
}
#define PIN_LED_DAY_R 10
#define PIN_LED_DAY_G 11
#define PIN_LED_DAY_B 12
#define PIN_LED_NIGHT_R 7
#define PIN_LED_NIGHT_G 8
#define PIN_LED_NIGHT_B 9

RGBLED SunLight(PIN_LED_DAY_R, PIN_LED_DAY_G, PIN_LED_DAY_B);
RGBLED MoonLight(PIN_LED_NIGHT_R, PIN_LED_NIGHT_G, PIN_LED_NIGHT_B);
RGB CLDayLight;
RGB CLNightLight;

void setup()
{
 CLDayLight = { 255, 255, 255 };
 CLNightLight = { 0, 0, 0 };
 SunLight.on(CLDayLight);
 SunLight.fadeTo(CLNightLight, 10000);
 MoonLight.fadeTo(CLDayLight, 10000);
 delay(500);
 Serial.println(F("Setup Done!"));
}

void loop()
{
 SunLight.update();
 MoonLight.update();
}

Serial Output (Each second line shows correctly fading up of the “MoonLight”, but incorrect fading down of the “SunLight”. Corresponding LEDs on the bradboard: Sunloght goes on and then off after ca. one sec, nightlight Fades on in 10 sec):

Starting Setup
Setup Done!
Time
0 = 255 + ( -255 * 502 / 10000 )
Time
12 = 0 + ( 255 * 503 / 10000 )
Time
0 = 255 + ( -255 * 703 / 10000 )
Time
17 = 0 + ( 255 * 704 / 10000 )
Time
0 = 255 + ( -255 * 904 / 10000 )
Time
23 = 0 + ( 255 * 905 / 10000 )
Time
0 = 255 + ( -255 * 1105 / 10000 )
Time
28 = 0 + ( 255 * 1106 / 10000 )
Time
0 = 255 + ( -255 * 1306 / 10000 )
Time
33 = 0 + ( 255 * 1307 / 10000 )
Time
0 = 255 + ( -255 * 1507 / 10000 )
Time
38 = 0 + ( 255 * 1508 / 10000 )
Time
0 = 255 + ( -255 * 1708 / 10000 )
Time
43 = 0 + ( 255 * 1709 / 10000 )
Time
0 = 255 + ( -255 * 1909 / 10000 )
Time
48 = 0 + ( 255 * 1910 / 10000 )
Time
0 = 255 + ( -255 * 2110 / 10000 )
Time
53 = 0 + ( 255 * 2111 / 10000 )
Time
0 = 255 + ( -255 * 2311 / 10000 )
Time
58 = 0 + ( 255 * 2312 / 10000 )
Time
0 = 255 + ( -255 * 2512 / 10000 )
Time
64 = 0 + ( 255 * 2513 / 10000 )
Time
0 = 255 + ( -255 * 2713 / 10000 )
Time
69 = 0 + ( 255 * 2714 / 10000 )
Time
0 = 255 + ( -255 * 2914 / 10000 )
Time
74 = 0 + ( 255 * 2915 / 10000 )
Time
0 = 255 + ( -255 * 3115 / 10000 )
Time
79 = 0 + ( 255 * 3116 / 10000 )
Time
0 = 255 + ( -255 * 3316 / 10000 )
Time
84 = 0 + ( 255 * 3317 / 10000 )
Time
0 = 255 + ( -255 * 3517 / 10000 )
Time
89 = 0 + ( 255 * 3518 / 10000 )
Time
0 = 255 + ( -255 * 3718 / 10000 )
Time
94 = 0 + ( 255 * 3719 / 10000 )
Time
0 = 255 + ( -255 * 3919 / 10000 )
Time
99 = 0 + ( 255 * 3920 / 10000 )
Time
0 = 255 + ( -255 * 4121 / 10000 )
Time
105 = 0 + ( 255 * 4122 / 10000 )
Time
0 = 255 + ( -255 * 4322 / 10000 )
Time
110 = 0 + ( 255 * 4323 / 10000 )
Time
0 = 255 + ( -255 * 4523 / 10000 )
Time
115 = 0 + ( 255 * 4524 / 10000 )
Time
0 = 255 + ( -255 * 4724 / 10000 )
Time
120 = 0 + ( 255 * 4725 / 10000 )
Time
0 = 255 + ( -255 * 4925 / 10000 )
Time
125 = 0 + ( 255 * 4926 / 10000 )
Time
0 = 255 + ( -255 * 5126 / 10000 )
Time
130 = 0 + ( 255 * 5127 / 10000 )
Time
0 = 255 + ( -255 * 5327 / 10000 )
Time
135 = 0 + ( 255 * 5328 / 10000 )
Time
0 = 255 + ( -255 * 5529 / 10000 )
Time
141 = 0 + ( 255 * 5530 / 10000 )
Time
0 = 255 + ( -255 * 5730 / 10000 )
Time
146 = 0 + ( 255 * 5731 / 10000 )
Time
0 = 255 + ( -255 * 5931 / 10000 )
Time
151 = 0 + ( 255 * 5932 / 10000 )
Time
0 = 255 + ( -255 * 6132 / 10000 )
Time
156 = 0 + ( 255 * 6133 / 10000 )
Time
0 = 255 + ( -255 * 6333 / 10000 )
Time
161 = 0 + ( 255 * 6334 / 10000 )
Time
0 = 255 + ( -255 * 6534 / 10000 )
Time
166 = 0 + ( 255 * 6535 / 10000 )
Time
0 = 255 + ( -255 * 6735 / 10000 )
Time
171 = 0 + ( 255 * 6736 / 10000 )
Time
0 = 255 + ( -255 * 6937 / 10000 )
Time
176 = 0 + ( 255 * 6938 / 10000 )
Time
0 = 255 + ( -255 * 7138 / 10000 )
Time
182 = 0 + ( 255 * 7139 / 10000 )
Time
0 = 255 + ( -255 * 7339 / 10000 )
Time
187 = 0 + ( 255 * 7340 / 10000 )
Time
0 = 255 + ( -255 * 7540 / 10000 )
Time
192 = 0 + ( 255 * 7541 / 10000 )
Time
0 = 255 + ( -255 * 7741 / 10000 )
Time
197 = 0 + ( 255 * 7742 / 10000 )
Time
0 = 255 + ( -255 * 7942 / 10000 )
Time
202 = 0 + ( 255 * 7943 / 10000 )
Time
0 = 255 + ( -255 * 8143 / 10000 )
Time
207 = 0 + ( 255 * 8144 / 10000 )
Time
0 = 255 + ( -255 * 8345 / 10000 )
Time
212 = 0 + ( 255 * 8346 / 10000 )
Time
0 = 255 + ( -255 * 8546 / 10000 )
Time
217 = 0 + ( 255 * 8547 / 10000 )
Time
0 = 255 + ( -255 * 8747 / 10000 )
Time
223 = 0 + ( 255 * 8748 / 10000 )
Time
0 = 255 + ( -255 * 8948 / 10000 )
Time
228 = 0 + ( 255 * 8949 / 10000 )
Time
0 = 255 + ( -255 * 9149 / 10000 )
Time
233 = 0 + ( 255 * 9150 / 10000 )
Time
0 = 255 + ( -255 * 9350 / 10000 )
Time
238 = 0 + ( 255 * 9351 / 10000 )
Time
0 = 255 + ( -255 * 9551 / 10000 )
Time
243 = 0 + ( 255 * 9552 / 10000 )
Time
0 = 255 + ( -255 * 9753 / 10000 )
Time
248 = 0 + ( 255 * 9754 / 10000 )
Time
0 = 255 + ( -255 * 9954 / 10000 )
Time
253 = 0 + ( 255 * 9955 / 10000 )

Clearly you missed my post stating a MCVE is great...

PS Note, fading in RGB isn't particularly nice... Fading in HSV is way nicer for us humans.

The wohle project is much more complex, so it´s quite hard to extract the essence for an example. But maybe someone gets the typecasting arithmethics right and can help me out. It´s really not an issue for wrong coding or compiler errors, but for the right application of the arithmethics here. My problem is, that I want to calculate int = int + int * unsigned long / unsigned long. So by typecasting the compiler either casts all variables as int (16bit,too small for the longs) or as unsigned long (32bit, but not signed, so does not work with the negative value) or as signed long (again not capable for the range of the unsigned long).

PhillipHommel: The wohle project is much more complex, so it´s quite hard to extract the essence for an example.

No it is not! You gave it a good start, you only forgot to simply try and compile the piece of code. You would have spotted (or at least the compiler) the errors right away!

Simply don't try to think like a mathematician! Aka, don't whack everything in a single statement! Most of the time splitting it up is way simpler

if(vStart > vEnd){
  //fade down
}
else{
  //fade up
}

Although you can probably fix it by casting the deltaTime to a singed int/long as well.

I suspect that your problem is caused by mixing signed and unsigned integers. Could your vDelta be promoting to unsigned long? Negative numbers would come out very large. Do everything in 'int' if it will fit. Use 'long int' if the values won't fit in an 'int'. Since it is integer math, be sure to multiply before you divide.

Try this version:

void fadeLED()
{
   now = millis();
   int vDelta = vEnd - vStart;

   // "Struggling here what typecasts to use where"
   int vAct = vStart + (vDelta * (long int)(now - tStart)) / (long int)tFade; 

   analogWrite(LEDpin, vAct);
}

Ok thanks for all your help. Splitting it up into a fading up and a fading down tree fixed it for now.

 int _rPin;
 int _gPin;
 int _bPin;

This is so you can use the class on any Arduino with negative pin numbers, right?

Or is it so that you can use it on all the Arduinos with 30000 pins?