fade using cosine

I found this sketch to fade an LED using cosine.

the math is beyond me.

I want to be able to find the point that it reaches 255
then have the LED just stay on.

also, when it reaches 0, and then just stay off.

the problem is that it reaches 1 for multiple iterations and not just once
and it can run for some time without reaching 255.

my question is how it actually works.

how does it count up, and then down ?
on reset, it starts at full bright. as if it starts at 255, then starts it's fade up and down.

int value;
int ledpin = 9;                           // light connected to digital PWM pin 9
int period = 2000;

void setup() 
{ 
Serial.begin(9600);
}  

void loop() { 
   value = 128+127*cos(2*PI/period*millis());
   analogWrite(ledpin, value);           
   Serial.print(millis());
   Serial.print(" - ");
   Serial.print(value);
   Serial.println();
}

I should have said that any value over 245 or so would be bright enough to have it just stay on.
and any value less than 5 as the threshold to stay off.

period is the cycle.
every half of period, is off, every full period is 255

for the lighthouse example
start at 1000 (time =millis()+1000)
then at 2,000 jump to a digitalWrite(led,HIGH)

so, ramp, up to full, then lock it in.

I can add a timer so that it will hold on for a desired duration, then fade to black,

I am still a puppet on a string. my understanding of sine is poor.
but at my age, I can live with just getting things to work.

Dude, it's a cosine function.

Cosine varies from 1 to -1, and then back again. So it goes from 1 to -1 and back again over 0-360 degrees. But we are not using degrees, because math. We use radians. There are 2*PI radians in a circle.

Because cos varies from 1 to -1, This:

128+127*cos(whatever)

varies from 1 to 255. Note that it never actually gets to zero. To do that, you'd want to use

255 * ((cos(whatever)+1)/2)

But that will only hit 255 if cos(whatever) is exactly -1 and 1, and that's a bit wobbly because floating-point and PI. Since you have to do floating point to int conversions anyway, I'd prefer:

(int) (255.99 * ((cos(whatever)+1)/2)))

Now as to when this hits its maximum and minimum values, that will be at 0 and Pi because there are 2Pi radians in a circle.

So in this case

cos(2*PI/period*millis())

the cosine will be 1 at multiples of period, and -1 at multiple-and-a-half of period. In the exaple code, it will start a 1 (counting from when the arduino was last reset) and go to -1 at millis()=1000, and back to 1 at millis()=2000.

(Why are there 2pi radians in a circle? Because the circumference of a circle is 2pi times the radius. A one length unit long arm rotating one radian per time unit will have a linear velocity of 1 unit speed at its tip. It makes a lot of calculus easier when an angle is the same as the length of the circumference subtended by that angle on a unit circle).

an old thread, but nevertheless just sharing an implementation

#define PER 20
#define REP 10

int8_t led = 0;
int8_t n = PER;
int cnt = 0;
uint8 p = 0;

void cosfade() {
  digitalWrite(LED_BUILTIN,led);
  led = ~led & 1;
  if(led)
    delay(PER-p);
  else
    delay(p);
  if(cnt>REP) {
	  float nf, perf;
	  n = n>PER?0:n+1;
	  nf = n * 1.0;
	  perf = PER * 1.0;
	  p = perf * ( cos(2 * PI * nf/perf) / 2.0 + 0.5 );
	  cnt=0;
  } else
	  cnt++;

}

cosfade() is called from loop()
requires <math.h> and an mcu with fpu would help

Oh this looks really useful... I would like to use this formula to fade an LED on when a switch is triggered until the LED hits a brightness setting, then keep it there. Then when the switch is off, reverse until the LED brightness is at 0 again. I'm a little lost in how to adapt this formula though! Maybe it's too late in the day. I'll have a go in the morning...

without trying this, I wonder what the comparison with a lookup table and execution speed vs memory use are like...
I’m sure someone has spent that 20 minutes...?

This is my code I used for my wavemaker.

//GENERAL SINE WAVE FUNCTION/////////////////////////////////////
void SineWave(int pin,                    //RAWPin number
             int period,                  //Sine wave time period
             int displace,                //Alternate if 2 pumps
             int Vpin,                    //ProgressBar
             int minflow,                 //MinFlow
             int maxflow)                 //MaxFlow
{
  float power = maxflow/2;
  int valuepump = power + power * cos(2 * PI / period * (displace - ts));
  if (valuepump >= maxflow)
  {
    valuepump = maxflow;
  }
  else if (valuepump <= minflow)
  {
    valuepump = minflow;
  }
  int progress = round((valuepump*100)/1023);
  analogWrite(pin, valuepump);
  Blynk.virtualWrite(Vpin, progress);
  analogWrite(pin, valuepump);
}
//CALLUP FUNCTION///////////////////////////////////////////////////////
SineWave(PUMP2, (F1T*1000), ((F1T/2)*1000), V13, MinF, MaxF);

I wonder what the comparison with a lookup table and execution speed vs memory use are like

Execution speed for cos() is much slower (About 1700 clocks, just for the cos() function, hundreds of clocks for each floating point math operation.)
Memory-wise, it'll depend on whether you're already using floating point and cos() in your code. (if not, that's quite a lot of code that it'll suck in.) Since you only need an 8bit value, a table-based approach could be pretty small.
Figure that since you only need 8bits of output, and probably 8bit resolution of the time is sufficient, and with floating point being 32bits, you must be doing at least 4 times more calculations than you could be. (In actuality, iit's much worse than that, because you're doing fancy math correctly.)

Good to know! So is there a more efficient way to achieve a nice progressive fade? I'm controlling a FET that drives around 30 leds on a backlight and I'd like them to come on smoothly but also don't want to kill the performance of the board...

A look-up table is a much more efficient way. It doesn't really make sense to keep repeating all those cosine calculations, especially when it gives the same answer for the same input.

So, I would definitely use a lookup table. You don't need to write it all into your code yourself - create the lookup table by calculation in setup(), to whatever granularity you need, then use it in loop(). Way faster.

The other advantage of creating the table during setup() is that you can customise the curve if you prefer a different type of fade.

Any examples for this look up table?

Proietti:
Any examples for this look up table?

Yes I would love to see this too please!

Proietti:
Any examples for this look up table?

Well, there are many ways of doing this. Here is a really crude one:

const float pi = 3.142;
float sineArray[360];

void setup()
{

  for (byte i = 0; i < 360; i++)
  {
    sineArray[i] = sin(2 * pi * ((float)i / 359));
  }
}

This creates a degrees-to-sine table for one full sine wave. Then you can find out the sine of n degrees by using sineArray[n]. This is vastly quicker than calculating sines repeatedly in your code, at the expense of using more memory.

Of course you wouldn't hard code those numbers - I did it like that to make it clear how it works. You should use sizeof() in real code.

If you want radians-to-pi then remove the '2 * pi'.

Obviously if you are using a cosine function, substitute that instead of sin();

Finally, if you are only going to use the first quarter (say) of a cosine wave, then you only need to create an array for the first 90 degrees.

If you want to save yet more space, create a table for just the even degree numbers (say), and then interpolate between the values either side for odd numbers. This shaves the array size in half but requires a little extra code when you look up a value from the table.

const uint8_t PROGMEM cosTable[256] = {
 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
 255, 255, 255, 254, 254, 254, 254, 254, 254, 253, 253, 253,
 253, 252, 252, 252, 252, 251, 251, 251, 251, 250, 250, 250,
 249, 249, 249, 248, 248, 247, 247, 247, 246, 246, 245, 245,
 244, 244, 244, 243, 243, 242, 242, 241, 241, 240, 239, 239,
 238, 238, 237, 237, 236, 235, 235, 234, 234, 233, 232, 232,
 231, 230, 230, 229, 228, 227, 227, 226, 225, 225, 224, 223,
 222, 221, 221, 220, 219, 218, 217, 217, 216, 215, 214, 213,
 212, 211, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202,
 201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190,
 189, 188, 187, 186, 185, 184, 183, 182, 181, 179, 178, 177,
 176, 175, 174, 173, 171, 170, 169, 168, 167, 166, 164, 163,
 162, 161, 159, 158, 157, 156, 155, 153, 152, 151, 149, 148,
 147, 146, 144, 143, 142, 140, 139, 138, 136, 135, 134, 132,
 131, 130, 128, 127, 126, 124, 123, 122, 120, 119, 117, 116,
 115, 113, 112, 110, 109, 108, 106, 105, 103, 102, 100, 99, 
 97, 96, 95, 93, 92, 90, 89, 87, 86, 84, 83, 81, 80, 78, 77,
 75, 74, 72, 71, 69, 68, 66, 65, 63, 62, 60, 59, 57, 56, 54,
 53, 51, 49, 48, 46, 45, 43, 42, 40, 39, 37, 36, 34, 32, 31,
 29, 28, 26, 25, 23, 21, 20, 18, 17, 15, 14, 12, 10, 9, 7, 6, 4, 3
};


/*
 * cos_i8(theta)
 * Theta in the angle in binaryrads (or something like that), where a
 * full circle is 1024 units.
 * output is an 8bit unsigned number.
 * Only handles the first quadrant (0 255 (=Pi/2 radians = 90 degrees)
 */
uint8_t cos_i8(uint16_t theta)
{
    theta &= 1023;    // mod 1024
    if (theta < 256) {  // first quadrant
	return pgm_read_byte(&cosTable[theta]);
    }
    return 0;   // error - input angle out of range.
}

Compiles, but not tested. I'm not sure why people are so fond of cos() for this purpose...
The cos table should be accurate; I generated it with a bit of python:

>>> def V(x):
...   frac = x/1024
...   rad = frac*2*3.14159
...   return int(256*math.cos(rad))

>>> for i in range(0, 255):
...   V(i)

I like westfw's solution - it's better than mine because the lookup table lives in program memory.

westfw:

const uint8_t PROGMEM cosTable[256] = {

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
...
29, 28, 26, 25, 23, 21, 20, 18, 17, 15, 14, 12, 10, 9, 7, 6, 4, 3
};

That's 255 values; the last one should be 1.

>>> for i in range(0, 255):

range(0, 255) yields values 0 to 254; range(256) is what you want here (start value defaults to 0).

westfw:

const uint8_t PROGMEM cosTable[256] = {

255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 254, 254, 254, 254, 254, 254, 253, 253, 253,
253, 252, 252, 252, 252, 251, 251, 251, 251, 250, 250, 250,
249, 249, 249, 248, 248, 247, 247, 247, 246, 246, 245, 245,
244, 244, 244, 243, 243, 242, 242, 241, 241, 240, 239, 239,
238, 238, 237, 237, 236, 235, 235, 234, 234, 233, 232, 232,
231, 230, 230, 229, 228, 227, 227, 226, 225, 225, 224, 223,
222, 221, 221, 220, 219, 218, 217, 217, 216, 215, 214, 213,
212, 211, 211, 210, 209, 208, 207, 206, 205, 204, 203, 202,
201, 200, 199, 198, 197, 196, 195, 194, 193, 192, 191, 190,
189, 188, 187, 186, 185, 184, 183, 182, 181, 179, 178, 177,
176, 175, 174, 173, 171, 170, 169, 168, 167, 166, 164, 163,
162, 161, 159, 158, 157, 156, 155, 153, 152, 151, 149, 148,
147, 146, 144, 143, 142, 140, 139, 138, 136, 135, 134, 132,
131, 130, 128, 127, 126, 124, 123, 122, 120, 119, 117, 116,
115, 113, 112, 110, 109, 108, 106, 105, 103, 102, 100, 99,
97, 96, 95, 93, 92, 90, 89, 87, 86, 84, 83, 81, 80, 78, 77,
75, 74, 72, 71, 69, 68, 66, 65, 63, 62, 60, 59, 57, 56, 54,
53, 51, 49, 48, 46, 45, 43, 42, 40, 39, 37, 36, 34, 32, 31,
29, 28, 26, 25, 23, 21, 20, 18, 17, 15, 14, 12, 10, 9, 7, 6, 4, 3
};

/*

  • cos_i8(theta)
  • Theta in the angle in binaryrads (or something like that), where a
  • full circle is 1024 units.
  • output is an 8bit unsigned number.
  • Only handles the first quadrant (0 255 (=Pi/2 radians = 90 degrees)
    */
    uint8_t cos_i8(uint16_t theta)
    {
        theta &= 1023;    // mod 1024
        if (theta < 256) {  // first quadrant
    return pgm_read_byte(&cosTable[theta]);
        }
        return 0;  // error - input angle out of range.
    }



Compiles, but not tested. I'm not sure why people are so fond of cos() for this purpose...
The cos table should be accurate; I generated it with a bit of python:




def V(x):
...  frac = x/1024
...  rad = frac23.14159
...  return int(256*math.cos(rad))

for i in range(0, 255):
...  V(i)

Fantastic, forgive me for being a little hazy but how do I then call this as a function to cycle up or down the LED using this array?

range(0, 255) yields values 0 to 254; range(256) is what you want here (start value defaults to 0).

Oops. Thanks for the correction. I did think about checking, but I figured if I was short, the final value(s) in the array would be zero, anyway, which was close enough. (that does explain why the min value was 3...)

forgive me for being a little hazy but how do I then call this as a function to cycle up or down the LED using this array?

Well, you'd just use cos_i8() where you would have done cos() before. Except the result is already scaled, and the input range is 0..255 for 90 degrees. Something like:

analogWrite(cos_i8(millis()/4);

In loop() will result in an LED that fades in a cosine-y way over 1 seconds, and they stays off for 3 sec (since the input value is out-of-range and the function returns 0.) Assuming that loop runs "often."

Thanks I'll have a try later... The call should be pretty infrequent, just when I turn the unit backlight on or off. So maybe I call it for 1 second only? Can it be called in reverse? Or started from further through the cycle