Skip to content

Commit 4f8de1f

Browse files
authored
add: general Fade(from, to, duration) method (#106)
* FadeOn/FadeOff now use generalized Breathe evaluator allowing us to get rid of classes formerly used for FadeOn/FadeOff * add generalized Fade(from,to,duration) function which internally configures the breathe-effect with min- and max-brightness correctly set up. * add fade from-to example * add pule example
1 parent 7bf7edc commit 4f8de1f

File tree

7 files changed

+136
-85
lines changed

7 files changed

+136
-85
lines changed

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ void loop() {
5959
* [FadeOn](#fadeon)
6060
* [FadeOn example](#fadeon-example)
6161
* [FadeOff](#fadeoff)
62+
* [Fade](#fade)
63+
* [Fade example](#fade-example)
6264
* [User provided brightness function](#user-provided-brightness-function)
6365
* [User provided brightness function example](#user-provided-brightness-function-example)
6466
* [Delays and repetitions](#delays-and-repetitions)
@@ -296,9 +298,33 @@ void loop() {
296298

297299
In FadeOff mode, the LED is smoothly faded off using PWM. The fade starts at
298300
100% brightness. Internally it is implemented as a mirrored version of the
299-
FadeOn function, i.e. FadeOn(t) = FadeOff(period-t). The `FadeOff()` method
301+
FadeOn function, i.e. FadeOff(t) = FadeOn(period-t). The `FadeOff()` method
300302
takes the period of the effect as argument.
301303

304+
#### Fade
305+
306+
The Fade effect allows to fade from any start value `from` to any target value
307+
`to` with the given duration. Internally it sets up a `FadeOn` or `FadeOff`
308+
effect and `MinBrightness` and `MaxBrightness` values properly. The `Fade`
309+
method take three argumens: `from`, `to` and `duration`.
310+
311+
<a href="examples/fade_from_to"><img alt="fade from-to" src="doc/fade_from-to.png" height=200></a>
312+
313+
##### Fade example
314+
315+
```c++
316+
#include <jled.h>
317+
318+
// fade from 100 to 200 with period 1000
319+
auto led = JLed(9).Fade(100, 200, 1000);
320+
321+
void setup() { }
322+
323+
void loop() {
324+
led.Update();
325+
}
326+
```
327+
302328
#### User provided brightness function
303329

304330
It is also possible to provide a user defined brightness evaluator. The class
@@ -545,6 +571,8 @@ Example sketches are provided in the [examples](examples/) directory.
545571
* [Candle effect](examples/candle)
546572
* [Fade LED on](examples/fade_on)
547573
* [Fade LED off](examples/fade_off)
574+
* [Fade from-to effect](examples/fade_from_to)
575+
* [Pulse effect](examples/pulse)
548576
* [Controlling multiple LEDs in parallel](examples/multiled)
549577
* [Controlling multiple LEDs in parallel (mbed)](examples/multiled_mbed)
550578
* [Controlling multiple LEDs sequentially](examples/sequence)

doc/cheat_sheet.jpg

300 KB
Loading

doc/fade_from-to.png

44.8 KB
Loading
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// JLed fade from-to example. Example randomly fades to a new level with
2+
// a random duration.
3+
// Copyright 2022 by Jan Delgado. All rights reserved.
4+
// https://github.com/jandelgado/jled
5+
#include <jled.h>
6+
7+
auto led = JLed(5).On(1); // start with LED turned on
8+
9+
void setup() {}
10+
11+
void loop() {
12+
static uint8_t last_to = 255;
13+
14+
if (!led.Update()) {
15+
// when effect is done (Update() returns false),
16+
// reconfigure fade effect using random values
17+
auto new_from = last_to;
18+
auto new_to = jled::rand8();
19+
auto duration = 250 + jled::rand8() * 4;
20+
last_to = new_to;
21+
led.Fade(new_from, new_to, duration).Repeat(1);
22+
}
23+
}

platformio.ini

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ default_envs = esp32
2121
;default_envs = sparkfun_samd21_dev_usb
2222

2323
; uncomment example to build
24-
;src_dir = examples/hello
24+
src_dir = examples/hello
2525
;src_dir = examples/morse
2626
;src_dir = examples/breathe
2727
;src_dir = examples/candle
@@ -33,7 +33,8 @@ default_envs = esp32
3333
;src_dir = examples/user_func
3434
;src_dir = examples/sequence
3535
;src_dir = examples/custom_hal
36-
src_dir = examples/pulse
36+
;src_dir = examples/pulse
37+
;src_dir = examples/fade_from_to
3738

3839
[env:nanoatmega328]
3940
platform = atmelavr

src/jled_base.h

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ static constexpr uint8_t kZeroBrightness = 0;
4343

4444
uint8_t fadeon_func(uint32_t t, uint16_t period);
4545
uint8_t rand8();
46-
void rand_seed(uint32_t s);
46+
void rand_seed(uint32_t s);
4747
uint8_t scale8(uint8_t val, uint8_t f);
4848
uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b);
4949

@@ -97,36 +97,6 @@ class BlinkBrightnessEvaluator : public CloneableBrightnessEvaluator {
9797
}
9898
};
9999

100-
// fade LED on
101-
class FadeOnBrightnessEvaluator : public CloneableBrightnessEvaluator {
102-
uint16_t period_;
103-
104-
public:
105-
FadeOnBrightnessEvaluator() = delete;
106-
explicit FadeOnBrightnessEvaluator(uint16_t period) : period_(period) {}
107-
BrightnessEvaluator* clone(void* ptr) const override {
108-
return new (ptr) FadeOnBrightnessEvaluator(*this);
109-
}
110-
uint16_t Period() const override { return period_; }
111-
uint8_t Eval(uint32_t t) const override { return fadeon_func(t, period_); }
112-
};
113-
114-
// fade LED off
115-
class FadeOffBrightnessEvaluator : public CloneableBrightnessEvaluator {
116-
uint16_t period_;
117-
118-
public:
119-
FadeOffBrightnessEvaluator() = delete;
120-
explicit FadeOffBrightnessEvaluator(uint16_t period) : period_(period) {}
121-
BrightnessEvaluator* clone(void* ptr) const override {
122-
return new (ptr) FadeOffBrightnessEvaluator(*this);
123-
}
124-
uint16_t Period() const override { return period_; }
125-
uint8_t Eval(uint32_t t) const override {
126-
return fadeon_func(period_ - t, period_);
127-
}
128-
};
129-
130100
// The breathe func is composed by fade-on, on and fade-off phases. For fading
131101
// we approximate the following function:
132102
// y(x) = exp(sin((t-period/4.) * 2. * PI / period)) - 0.36787944) * 108.)
@@ -160,6 +130,10 @@ class BreatheBrightnessEvaluator : public CloneableBrightnessEvaluator {
160130
else
161131
return fadeon_func(Period() - t, duration_fade_off_);
162132
}
133+
134+
uint16_t DurationFadeOn() const { return duration_fade_on_; }
135+
uint16_t DurationFadeOff() const { return duration_fade_off_; }
136+
uint16_t DurationOn() const { return duration_on_; }
163137
};
164138

165139
class CandleBrightnessEvaluator : public CloneableBrightnessEvaluator {
@@ -281,14 +255,25 @@ class TJLed {
281255

282256
// Fade LED on
283257
B& FadeOn(uint16_t duration) {
284-
return SetBrightnessEval(new (brightness_eval_buf_)
285-
FadeOnBrightnessEvaluator(duration));
258+
return SetBrightnessEval(new (
259+
brightness_eval_buf_) BreatheBrightnessEvaluator(duration, 0, 0));
286260
}
287261

288262
// Fade LED off - acutally is just inverted version of FadeOn()
289263
B& FadeOff(uint16_t duration) {
290-
return SetBrightnessEval(new (brightness_eval_buf_)
291-
FadeOffBrightnessEvaluator(duration));
264+
return SetBrightnessEval(new (
265+
brightness_eval_buf_) BreatheBrightnessEvaluator(0, 0, duration));
266+
}
267+
268+
// Fade from "from" to "to" with period "duration". Sets up the breathe
269+
// effect with the proper parameters and sets Min/Max brightness to reflect
270+
// levels specified by "from" and "to".
271+
B& Fade(uint8_t from, uint8_t to, uint16_t duration) {
272+
if (from < to) {
273+
return FadeOn(duration).MinBrightness(from).MaxBrightness(to);
274+
} else {
275+
return FadeOff(duration).MinBrightness(to).MaxBrightness(from);
276+
}
292277
}
293278

294279
// Set effect to Breathe, with the given period time in ms.
@@ -388,9 +373,7 @@ class TJLed {
388373
return (now & 255) != last_update_time_;
389374
}
390375

391-
void trackLastUpdateTime(uint32_t t) {
392-
last_update_time_ = (t & 255);
393-
}
376+
void trackLastUpdateTime(uint32_t t) { last_update_time_ = (t & 255); }
394377

395378
// update brightness of LED using the given brightness evaluator
396379
// (brightness) ________________
@@ -405,8 +388,8 @@ class TJLed {
405388
if (state_ == ST_STOPPED || !brightness_eval_) return false;
406389

407390
if (state_ == ST_INIT) {
408-
time_start_ = now + delay_before_;
409-
state_ = ST_RUNNING;
391+
time_start_ = now + delay_before_;
392+
state_ = ST_RUNNING;
410393
} else {
411394
// no need to process updates twice during one time tick.
412395
if (!timeChangedSinceLastUpdate(now)) return true;
@@ -454,7 +437,7 @@ class TJLed {
454437
}
455438

456439
public:
457-
// Number of bits used to control brightness with Min/MaxBrightness().
440+
// Number of bits used to control brightness with Min/MaxBrightness().
458441
static constexpr uint8_t kBitsBrightness = 8;
459442
static constexpr uint8_t kBrightnessStep = 1;
460443

test/test_jled.cpp

Lines changed: 57 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ using jled::BreatheBrightnessEvaluator;
1414
using jled::BrightnessEvaluator;
1515
using jled::CandleBrightnessEvaluator;
1616
using jled::ConstantBrightnessEvaluator;
17-
using jled::FadeOffBrightnessEvaluator;
18-
using jled::FadeOnBrightnessEvaluator;
1917
using jled::TJLed;
2018

2119
// TestJLed is a JLed class using the HalMock for tests. This allows to
@@ -112,9 +110,14 @@ TEST_CASE("using Breathe() configures BreatheBrightnessEvaluator", "[jled]") {
112110
using TestJLed::TestJLed;
113111
static void test() {
114112
TestableJLed jled(1);
115-
jled.Breathe(0);
113+
jled.Breathe(100, 200, 300);
116114
REQUIRE(dynamic_cast<BreatheBrightnessEvaluator *>(
117115
jled.brightness_eval_) != nullptr);
116+
auto eval = dynamic_cast<BreatheBrightnessEvaluator *>(
117+
jled.brightness_eval_);
118+
CHECK(100 == eval->DurationFadeOn());
119+
CHECK(200 == eval->DurationOn());
120+
CHECK(300 == eval->DurationFadeOff());
118121
}
119122
};
120123
TestableJLed::test();
@@ -140,23 +143,68 @@ TEST_CASE("using Fadeon(), FadeOff() configures Fade-BrightnessEvaluators",
140143
public:
141144
using TestJLed::TestJLed;
142145
static void test() {
143-
SECTION("FadeOff() initializes with FadeOffBrightnessEvaluator") {
146+
SECTION("FadeOff() initializes with BreatheBrightnessEvaluator") {
144147
TestableJLed jled(1);
145-
jled.FadeOff(0);
146-
REQUIRE(dynamic_cast<FadeOffBrightnessEvaluator *>(
148+
jled.FadeOff(100);
149+
REQUIRE(dynamic_cast<BreatheBrightnessEvaluator *>(
147150
jled.brightness_eval_) != nullptr);
151+
auto eval = dynamic_cast<BreatheBrightnessEvaluator *>(
152+
jled.brightness_eval_);
153+
CHECK(0 == eval->DurationFadeOn());
154+
CHECK(0 == eval->DurationOn());
155+
CHECK(100 == eval->DurationFadeOff());
148156
}
149-
SECTION("FadeOn() initializes with FadeOnBrightnessEvaluator") {
157+
SECTION("FadeOn() initializes with BreatheBrightnessEvaluator") {
150158
TestableJLed jled(1);
151-
jled.FadeOn(0);
152-
REQUIRE(dynamic_cast<FadeOnBrightnessEvaluator *>(
159+
jled.FadeOn(100);
160+
REQUIRE(dynamic_cast<BreatheBrightnessEvaluator *>(
153161
jled.brightness_eval_) != nullptr);
162+
auto eval = dynamic_cast<BreatheBrightnessEvaluator *>(
163+
jled.brightness_eval_);
164+
CHECK(100 == eval->DurationFadeOn());
165+
CHECK(0 == eval->DurationOn());
166+
CHECK(0 == eval->DurationFadeOff());
154167
}
155168
}
156169
};
157170
TestableJLed::test();
158171
}
159172

173+
TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") {
174+
class TestableJLed : public TestJLed {
175+
public:
176+
using TestJLed::TestJLed;
177+
static void test() {
178+
SECTION("fade with from < to") {
179+
TestableJLed jled(1);
180+
jled.Fade(100, 200, 300);
181+
REQUIRE(dynamic_cast<BreatheBrightnessEvaluator *>(
182+
jled.brightness_eval_) != nullptr);
183+
auto eval = dynamic_cast<BreatheBrightnessEvaluator *>(
184+
jled.brightness_eval_);
185+
CHECK(300 == eval->DurationFadeOn());
186+
CHECK(0 == eval->DurationOn());
187+
CHECK(0 == eval->DurationFadeOff());
188+
CHECK(100 == jled.MinBrightness());
189+
CHECK(200 == jled.MaxBrightness());
190+
}
191+
SECTION("fade with from >= to") {
192+
TestableJLed jled(1);
193+
jled.Fade(200, 100, 300);
194+
REQUIRE(dynamic_cast<BreatheBrightnessEvaluator *>(
195+
jled.brightness_eval_) != nullptr);
196+
auto eval = dynamic_cast<BreatheBrightnessEvaluator *>(
197+
jled.brightness_eval_);
198+
CHECK(0 == eval->DurationFadeOn());
199+
CHECK(0 == eval->DurationOn());
200+
CHECK(300 == eval->DurationFadeOff());
201+
CHECK(100 == jled.MinBrightness());
202+
CHECK(200 == jled.MaxBrightness());
203+
}
204+
}
205+
};
206+
TestableJLed::test();
207+
}
160208
TEST_CASE("UserFunc() allows to use a custom brightness evaluator", "[jled]") {
161209
class TestableJLed : public TestJLed {
162210
public:
@@ -205,38 +253,6 @@ TEST_CASE("CandleBrightnessEvaluator simulated candle flickering", "[jled]") {
205253
CHECK(eval.Eval(999) > 0);
206254
}
207255

208-
TEST_CASE("FadeOnEvaluator evaluates to expected brightness curve", "[jled]") {
209-
constexpr auto kPeriod = 2000;
210-
211-
auto evalOn = FadeOnBrightnessEvaluator(kPeriod);
212-
213-
CHECK(kPeriod == evalOn.Period());
214-
215-
const std::map<uint32_t, uint8_t> test_values = {
216-
{0, 0}, {500, 13}, {1000, 68}, {1500, 179},
217-
{1999, 255}, {2000, 255}, {10000, 255}};
218-
219-
for (const auto &x : test_values) {
220-
CHECK(x.second == evalOn.Eval(x.first));
221-
}
222-
}
223-
224-
TEST_CASE("FadeOffEvaluator evaluates to expected brightness curve", "[jled]") {
225-
constexpr auto kPeriod = 2000;
226-
227-
// note: FadeOff is invervted FadeOn
228-
auto evalOff = FadeOffBrightnessEvaluator(kPeriod);
229-
230-
CHECK(kPeriod == evalOff.Period());
231-
const std::map<uint32_t, uint8_t> test_values = {
232-
{0, 0}, {500, 13}, {1000, 68}, {1500, 179},
233-
{1999, 255}, {2000, 255}, {10000, 255}};
234-
235-
for (const auto &x : test_values) {
236-
CHECK(x.second == evalOff.Eval(kPeriod - x.first));
237-
}
238-
}
239-
240256
TEST_CASE(
241257
"BreatheEvaluator evaluates to bell curve distributed brightness curve",
242258
"[jled]") {

0 commit comments

Comments
 (0)