Non-blocking “breathing” led

I would not use sin as it’s not easy to use.and I suspect processor intensive . but , maths wise ….
The sin function goes from 0-1 ,for 0-180deg (pi/2 for Arduino ) so you have a multiplier in front of it, say 255 to give 0-255 .

Y= 255sin(X)

But you want to go 20 to 255 so…

Y= 20+ 235sin(X) is the function you want , translating to non blocking is next … millis()?

Or to hell with it and ask google and modify output if need by scaling it using MAP
Example

Try this

To modify the formula to have the LED value go from 20 to 255 and back, you can adjust the formula like this:

float val = (exp(sin(i/2000.0*PI*10)) - 0.36787944)*108.0;
val = 20 + (val * 2.35); // Scale the value to go from 20 to 255

This modification scales the output of the formula to fit within the 20 to 255 range without freezing the animation.

this calulates too big values
as you can see here

1 184.45
2 188.53
3 192.67
4 196.88
5 201.15
6 205.48
7 209.87
8 214.32
9 218.83
10 223.41
11 228.04
12 232.74
13 237.49
14 242.30
15 247.17
16 252.09
17 257.07
18 262.10
19 267.19
20 272.33
21 277.52
22 282.76
23 288.05
24 293.38
25 298.76
26 304.18
27 309.64
28 315.14
29 320.69
30 326.26
31 331.87
32 337.51
33 343.18
34 348.88
35 354.60
36 360.34
37 366.10
38 371.88
39 377.67
40 383.47
41 389.28
42 395.09
43 400.91
44 406.72
45 412.53
46 418.33
47 424.11
48 429.89
49 435.64
50 441.37
51 447.07
52 452.74
53 458.38
54 463.99
55 469.55
56 475.06
57 480.53
58 485.94
59 491.30
60 496.59
61 501.82
62 506.98
63 512.07
64 517.08
65 522.00
66 526.85
67 531.60
68 536.26
69 540.83
70 545.29
71 549.65
72 553.90
73 558.04
74 562.06
75 565.97
76 569.75
77 573.40
78 576.93
79 580.32
80 583.58
81 586.70
82 589.68
83 592.51
84 595.19
85 597.73
86 600.12
87 602.35
88 604.42
89 606.34
90 608.09
91 609.68
92 611.11
93 612.38
94 613.48
95 614.41
96 615.17
97 615.77
98 616.19
99 616.45
100 616.53
101 616.45
102 616.19
103 615.77
104 615.17
105 614.41
106 613.48
107 612.38
108 611.11
109 609.6

the values go up to 616 because you scale up the values used before with an additional factor of 2.35

the scaling must be done with the factor 108

    float val = (exp(sin(i / 2000.0 * PI * 10)) - 0.36787944) * (108.0 * 235.0/255.0);
    val = 20 + val; // Scale the value to go from 20 to 255

where the calculation 108.0 * 235.0/255.0 results in 99.53

You can test it with this code which prints the values to the serial monitor


/*

  NonBlockingBreathingLed 0.1
  by Luca Soltoggio - 2015
  12 May 2015
  http://www.arduinoelettronica.com/
  https://arduinoelectronics.wordpress.com/
  http://minibianpi.wodpress.com/

  Use a exp + sin function to recreate a
  non-blocking breathing led effect

  Released under GPL v.2 license

*/

#include <math.h>
#define ledPin 13

#define div2000 2000.0
#define minus0_36 0.55 // 0.36787944
#define mult108 100.0  // 108
#define baseOffset 38  // 0


/*
  #define div2000 2000.0
  #define minus0_36 0.36787944
  #define mult108 108
  #define baseOffset 0
*/

int i = 0;
int breathe_delay = 15;   // delay between loops
unsigned long breathe_time = millis();
void setup() {
  Serial.begin(115200);
  OneNonBlockingBreath();
}


void loop() {
  //nonBlockingBreath();  // call the nonblocking function
  // yourOtherCodeHere();
}

void OneNonBlockingBreath() {

  for (int i = 0; i < 1500; i++) {
    //if ( (breathe_time + breathe_delay) < millis() ) {
    breathe_time = millis();
    //float val = (exp ( sin( i / 2000.0 * PI * 10) ) - 0.36787944) * 108.0;
    //float val = (exp ( sin( i / div2000 * PI * 10) ) - minus0_36) * mult108 + baseOffset;
    //float val = (exp(sin(i / 2000.0 * PI * 10)) - 0.36787944) * 108.0;
    //float val = (exp(sin(i / 2000.0 * PI * 10)) - 0.36787944) * (108.0 * 235.0/255.0);
    float val = (exp( sin(i / 2000.0 * PI * 10)) - 0.36787944) * 108.0;
    val = 20 + (val * 2.35); // Scale the value to go from 20 to 255
    Serial.print(i);
    Serial.print(" ");

    Serial.println(val);
    //(exp (  sin(i / 2000.0 * PI * 10) ) - 0.36787944)    *  108.0;
    // this is the math function recreating the effect
    analogWrite(ledPin, val);  // PWM
    //i = i + 1;
    //}
  }
}


void nonBlockingBreath() {
  if ( (breathe_time + breathe_delay) < millis() ) {
    breathe_time = millis();
    //float val = (exp ( sin( i / 2000.0 * PI * 10) ) - 0.36787944) * 108.0;
    //Serial.println(val);
    float val = (exp ( sin( i / div2000 * PI * 10) ) - minus0_36) * mult108 + baseOffset;
    //(exp (  sin(i / 2000.0 * PI * 10) ) - 0.36787944)    *  108.0;
    // this is the math function recreating the effect
    analogWrite(ledPin, val);  // PWM
    i = i + 1;
  }
}

best regards Stefan

@codingapacalyspe don't post stuff you haven't tested.

And @mancera1979's version in #18 is identical to @alto777's #17, if you apply a bit of high school algebra. The constants may not end up exactly, but the rearranging of the scaling and shifting is only that. Rearranging.

As for omitting the exp() function and driving it from the sine function directly, I cannot say what the OP sees or is going for.

You might as well suggest using exp() driven by a triangle wave.

a7

More like Middle School arithmetic.

Same correct solution also provided in Post #13 and Post #24.

Come on folks, this just isn't that hard. If @mastino2 is OK with performance using floats, trig, and exponential, then the "problem" has been solved by trivial math.

Of course, as many have pointed out, this is a horrendous solution in terms of computation efficiency. Many integer-based techniques exisit.

It was a long time ago, whatever level!

And sry, yes your #13 is the same same solution.

I only started looking closer when an easy problem didn't seem to be getting solved, then was solved by AI but presented without testing, then… I recognized*

0.36787944

as 2.71828^-1, and looked closer at the original. It then started to make some kind of sense…

*from old game played with "scientific" calculators.

a7

My millis_soft_pulsating_led.ino uses a sin() and a pow().
Try it in Wokwi: millis_soft_pulsating_led.ino - Wokwi ESP32, STM32, Arduino Simulator

For my millis_heartbeat.ino, I used the standard deviation as a inspiration for the curve.
Try it in Wokwi: millis_led_heartbeat.ino - Wokwi ESP32, STM32, Arduino Simulator

The PWM signal is only 8-bits and the difference in brightness between PWM level 0 and 1 is large. By using a minimum value of 1, it looks better.

Better, try those sketches IRL, with the LEDs you have in front of you. LEDs are a weak point of any simulator.

I have never been blinded by a wokwi neopixel!

a7

Hello mastino2

Consider:

void setup()
{
  pinMode(9, OUTPUT);
}
void loop()
{
  uint32_t currentMillis = millis();
  uint8_t pwmValue = 255 * ((sin(2 * PI * currentMillis / 2000) + 1) / 2);
  pwmValue < 20 ? analogWrite(9, 20) : analogWrite(9, pwmValue);
}

Have a nice day and enjoy coding in C++.

The OP tried clamping the low end and said

if(val < 20) val = 20;
but I can clearly see this freezing and it looks not so nice.

  uint8_t pwmValue = 235* ((sin(2 * PI * currentMillis / 2000) + 1) / 2) + 20;

Would be closer, has already perhaps been suggested and for whatever reason also steps away from using exp().

I have to say after all this time that using a never-decreasing argument for sin() seems odd, and I wnoder at what point that argument will begin to make no sense, or if will ever be a problem.

It does work for the first few seconds I've ever tested any of the solutions and "solutions".

a7

Well, OP left the room after post#6, so I guess we may never know where this lead to. :person_shrugging:

It should be added to the "How to get the most..." sticky that n00bs should closely monitor their topics - not pose a question figuring to swing by sometime next week or so and otherwise effortlessly pick up a perfect, gift-boxed solution.

Agreed.  But, sometimes real life gets in the way of good intentions.

Thank you very much! I am so happy to get so many answers from clever people!
After trying all suggestion, I will take the last one

uint8_t pwmValue = 235* ((sin(2 * PI * currentMillis / 6000) + 1) / 2) + 20;

in my setup this line makes the led breathing most realistic.
Also the code from Koepel is very useful, I believe I will use heartbeat in my next project!
Many thanks one more time for all answers!

I don't know what this proves or doesn't, but it looks like just using a never-decreasing argument to sin() is asking for trouble:

float val;
# define PI 3.14159265358979

void setup() {
  Serial.begin(115200);
  Serial.println("what?\n");
}

float never;
float adjust;

void loop()
{
  static unsigned long counter;

  if (counter % 5000 == 0) {
    Serial.print(counter); Serial.print("  ");
    Serial.print(sin(never), 4); Serial.print("  ");
    Serial.print(sin(adjust), 4); Serial.print("  ");

    Serial.println("");
  }

  never += PI/180.0;

  adjust += PI/180.0;
  if (adjust > 2 * PI) adjust -= 2 * PI;

  counter++;
}

Gives this output

what?

0       0.0000   0.0000  
5000   -0.6409  -0.6430  
10000  -0.9819  -0.9849  
15000  -0.8807  -0.8656  
20000  -0.3829  -0.3408  
25000   0.2873   0.3435  
30000   0.8281   0.8670  
35000   0.9960   0.9844  
40000   0.7154   0.6408  
45000   0.1127  -0.0029  

showing a benefit to the poor man's modulo calculation to keep the argument between 0 and 2 * PI.

Perhaps never a matter in a fading LED.

a7

Sorry for delay. Just I need go to work everyday. Is forbidden to use phone on my workplace.

You are going to make time since startup a factor? So long.

yes, I was thinking about it too, but how can I change it?
If I am true I can not understand this formula.. only copy/paste.

I guess you will to ask for the explanation. And I'm curious who will answer what. (Short keywords or an easy to understand explanation)

For understanding, deconstruct the expression into multiple components?
e.g.

  // uint8_t pwmValue = 235* ((sin(2 * PI * currentMillis / 6000) + 1) / 2) + 20;
  // is equivalent to 

  float phase_angle = 2 * PI * currentMillis / 6000 ;    // Linear phase of 2 Pi radians in 6000 milliseconds (6 seconds per revolution)
  float sine_wave = sin(phase_angle) ;  // Generate sine wave in range -1 to 1 with 6 second period
  float positive_sine = (sine_wave + 1)/2 ;   // Sine wave in range 0 to 1 with 6 second period
  float sine_20_to_255 = (255 - 20) * positive_sine + 20 ;  // Sine wave in range 20 to 255 with 6 second period
  uint8_t pwmValue = sine_20_to_255 ;   // convert float to uint8_t