diff --git a/README.md b/README.md index 8323f47..4bba022 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ void loop() { * [FadeOn](#fadeon) * [FadeOn example](#fadeon-example) * [FadeOff](#fadeoff) + * [Fade](#fade) + * [Fade example](#fade-example) * [User provided brightness function](#user-provided-brightness-function) * [User provided brightness function example](#user-provided-brightness-function-example) * [Delays and repetitions](#delays-and-repetitions) @@ -296,9 +298,33 @@ void loop() { In FadeOff mode, the LED is smoothly faded off using PWM. The fade starts at 100% brightness. Internally it is implemented as a mirrored version of the -FadeOn function, i.e. FadeOn(t) = FadeOff(period-t). The `FadeOff()` method +FadeOn function, i.e. FadeOff(t) = FadeOn(period-t). The `FadeOff()` method takes the period of the effect as argument. +#### Fade + +The Fade effect allows to fade from any start value `from` to any target value +`to` with the given duration. Internally it sets up a `FadeOn` or `FadeOff` +effect and `MinBrightness` and `MaxBrightness` values properly. The `Fade` +method take three argumens: `from`, `to` and `duration`. + +fade from-to + +##### Fade example + +```c++ +#include + +// fade from 100 to 200 with period 1000 +auto led = JLed(9).Fade(100, 200, 1000); + +void setup() { } + +void loop() { + led.Update(); +} +``` + #### User provided brightness function 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. * [Candle effect](examples/candle) * [Fade LED on](examples/fade_on) * [Fade LED off](examples/fade_off) +* [Fade from-to effect](examples/fade_from_to) +* [Pulse effect](examples/pulse) * [Controlling multiple LEDs in parallel](examples/multiled) * [Controlling multiple LEDs in parallel (mbed)](examples/multiled_mbed) * [Controlling multiple LEDs sequentially](examples/sequence) diff --git a/doc/cheat_sheet.jpg b/doc/cheat_sheet.jpg index c20c587..1d31fdd 100644 Binary files a/doc/cheat_sheet.jpg and b/doc/cheat_sheet.jpg differ diff --git a/doc/fade_from-to.png b/doc/fade_from-to.png new file mode 100644 index 0000000..05c351c Binary files /dev/null and b/doc/fade_from-to.png differ diff --git a/examples/fade_from_to/fade_from_to.ino b/examples/fade_from_to/fade_from_to.ino new file mode 100644 index 0000000..16251da --- /dev/null +++ b/examples/fade_from_to/fade_from_to.ino @@ -0,0 +1,23 @@ +// JLed fade from-to example. Example randomly fades to a new level with +// a random duration. +// Copyright 2022 by Jan Delgado. All rights reserved. +// https://github.com/jandelgado/jled +#include + +auto led = JLed(5).On(1); // start with LED turned on + +void setup() {} + +void loop() { + static uint8_t last_to = 255; + + if (!led.Update()) { + // when effect is done (Update() returns false), + // reconfigure fade effect using random values + auto new_from = last_to; + auto new_to = jled::rand8(); + auto duration = 250 + jled::rand8() * 4; + last_to = new_to; + led.Fade(new_from, new_to, duration).Repeat(1); + } +} diff --git a/platformio.ini b/platformio.ini index eca827f..eb98c62 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ default_envs = esp32 ;default_envs = sparkfun_samd21_dev_usb ; uncomment example to build -;src_dir = examples/hello +src_dir = examples/hello ;src_dir = examples/morse ;src_dir = examples/breathe ;src_dir = examples/candle @@ -33,7 +33,8 @@ default_envs = esp32 ;src_dir = examples/user_func ;src_dir = examples/sequence ;src_dir = examples/custom_hal -src_dir = examples/pulse +;src_dir = examples/pulse +;src_dir = examples/fade_from_to [env:nanoatmega328] platform = atmelavr diff --git a/src/jled_base.h b/src/jled_base.h index 5a1d857..9e13a9e 100644 --- a/src/jled_base.h +++ b/src/jled_base.h @@ -43,7 +43,7 @@ static constexpr uint8_t kZeroBrightness = 0; uint8_t fadeon_func(uint32_t t, uint16_t period); uint8_t rand8(); -void rand_seed(uint32_t s); +void rand_seed(uint32_t s); uint8_t scale8(uint8_t val, uint8_t f); uint8_t lerp8by8(uint8_t val, uint8_t a, uint8_t b); @@ -97,36 +97,6 @@ class BlinkBrightnessEvaluator : public CloneableBrightnessEvaluator { } }; -// fade LED on -class FadeOnBrightnessEvaluator : public CloneableBrightnessEvaluator { - uint16_t period_; - - public: - FadeOnBrightnessEvaluator() = delete; - explicit FadeOnBrightnessEvaluator(uint16_t period) : period_(period) {} - BrightnessEvaluator* clone(void* ptr) const override { - return new (ptr) FadeOnBrightnessEvaluator(*this); - } - uint16_t Period() const override { return period_; } - uint8_t Eval(uint32_t t) const override { return fadeon_func(t, period_); } -}; - -// fade LED off -class FadeOffBrightnessEvaluator : public CloneableBrightnessEvaluator { - uint16_t period_; - - public: - FadeOffBrightnessEvaluator() = delete; - explicit FadeOffBrightnessEvaluator(uint16_t period) : period_(period) {} - BrightnessEvaluator* clone(void* ptr) const override { - return new (ptr) FadeOffBrightnessEvaluator(*this); - } - uint16_t Period() const override { return period_; } - uint8_t Eval(uint32_t t) const override { - return fadeon_func(period_ - t, period_); - } -}; - // The breathe func is composed by fade-on, on and fade-off phases. For fading // we approximate the following function: // y(x) = exp(sin((t-period/4.) * 2. * PI / period)) - 0.36787944) * 108.) @@ -160,6 +130,10 @@ class BreatheBrightnessEvaluator : public CloneableBrightnessEvaluator { else return fadeon_func(Period() - t, duration_fade_off_); } + + uint16_t DurationFadeOn() const { return duration_fade_on_; } + uint16_t DurationFadeOff() const { return duration_fade_off_; } + uint16_t DurationOn() const { return duration_on_; } }; class CandleBrightnessEvaluator : public CloneableBrightnessEvaluator { @@ -281,14 +255,25 @@ class TJLed { // Fade LED on B& FadeOn(uint16_t duration) { - return SetBrightnessEval(new (brightness_eval_buf_) - FadeOnBrightnessEvaluator(duration)); + return SetBrightnessEval(new ( + brightness_eval_buf_) BreatheBrightnessEvaluator(duration, 0, 0)); } // Fade LED off - acutally is just inverted version of FadeOn() B& FadeOff(uint16_t duration) { - return SetBrightnessEval(new (brightness_eval_buf_) - FadeOffBrightnessEvaluator(duration)); + return SetBrightnessEval(new ( + brightness_eval_buf_) BreatheBrightnessEvaluator(0, 0, duration)); + } + + // Fade from "from" to "to" with period "duration". Sets up the breathe + // effect with the proper parameters and sets Min/Max brightness to reflect + // levels specified by "from" and "to". + B& Fade(uint8_t from, uint8_t to, uint16_t duration) { + if (from < to) { + return FadeOn(duration).MinBrightness(from).MaxBrightness(to); + } else { + return FadeOff(duration).MinBrightness(to).MaxBrightness(from); + } } // Set effect to Breathe, with the given period time in ms. @@ -388,9 +373,7 @@ class TJLed { return (now & 255) != last_update_time_; } - void trackLastUpdateTime(uint32_t t) { - last_update_time_ = (t & 255); - } + void trackLastUpdateTime(uint32_t t) { last_update_time_ = (t & 255); } // update brightness of LED using the given brightness evaluator // (brightness) ________________ @@ -405,8 +388,8 @@ class TJLed { if (state_ == ST_STOPPED || !brightness_eval_) return false; if (state_ == ST_INIT) { - time_start_ = now + delay_before_; - state_ = ST_RUNNING; + time_start_ = now + delay_before_; + state_ = ST_RUNNING; } else { // no need to process updates twice during one time tick. if (!timeChangedSinceLastUpdate(now)) return true; @@ -454,7 +437,7 @@ class TJLed { } public: - // Number of bits used to control brightness with Min/MaxBrightness(). + // Number of bits used to control brightness with Min/MaxBrightness(). static constexpr uint8_t kBitsBrightness = 8; static constexpr uint8_t kBrightnessStep = 1; diff --git a/test/test_jled.cpp b/test/test_jled.cpp index 3d4fc40..fc81e42 100644 --- a/test/test_jled.cpp +++ b/test/test_jled.cpp @@ -14,8 +14,6 @@ using jled::BreatheBrightnessEvaluator; using jled::BrightnessEvaluator; using jled::CandleBrightnessEvaluator; using jled::ConstantBrightnessEvaluator; -using jled::FadeOffBrightnessEvaluator; -using jled::FadeOnBrightnessEvaluator; using jled::TJLed; // TestJLed is a JLed class using the HalMock for tests. This allows to @@ -112,9 +110,14 @@ TEST_CASE("using Breathe() configures BreatheBrightnessEvaluator", "[jled]") { using TestJLed::TestJLed; static void test() { TestableJLed jled(1); - jled.Breathe(0); + jled.Breathe(100, 200, 300); REQUIRE(dynamic_cast( jled.brightness_eval_) != nullptr); + auto eval = dynamic_cast( + jled.brightness_eval_); + CHECK(100 == eval->DurationFadeOn()); + CHECK(200 == eval->DurationOn()); + CHECK(300 == eval->DurationFadeOff()); } }; TestableJLed::test(); @@ -140,23 +143,68 @@ TEST_CASE("using Fadeon(), FadeOff() configures Fade-BrightnessEvaluators", public: using TestJLed::TestJLed; static void test() { - SECTION("FadeOff() initializes with FadeOffBrightnessEvaluator") { + SECTION("FadeOff() initializes with BreatheBrightnessEvaluator") { TestableJLed jled(1); - jled.FadeOff(0); - REQUIRE(dynamic_cast( + jled.FadeOff(100); + REQUIRE(dynamic_cast( jled.brightness_eval_) != nullptr); + auto eval = dynamic_cast( + jled.brightness_eval_); + CHECK(0 == eval->DurationFadeOn()); + CHECK(0 == eval->DurationOn()); + CHECK(100 == eval->DurationFadeOff()); } - SECTION("FadeOn() initializes with FadeOnBrightnessEvaluator") { + SECTION("FadeOn() initializes with BreatheBrightnessEvaluator") { TestableJLed jled(1); - jled.FadeOn(0); - REQUIRE(dynamic_cast( + jled.FadeOn(100); + REQUIRE(dynamic_cast( jled.brightness_eval_) != nullptr); + auto eval = dynamic_cast( + jled.brightness_eval_); + CHECK(100 == eval->DurationFadeOn()); + CHECK(0 == eval->DurationOn()); + CHECK(0 == eval->DurationFadeOff()); } } }; TestableJLed::test(); } +TEST_CASE("using Fade() configures BreatheBrightnessEvaluator", "[jled]") { + class TestableJLed : public TestJLed { + public: + using TestJLed::TestJLed; + static void test() { + SECTION("fade with from < to") { + TestableJLed jled(1); + jled.Fade(100, 200, 300); + REQUIRE(dynamic_cast( + jled.brightness_eval_) != nullptr); + auto eval = dynamic_cast( + jled.brightness_eval_); + CHECK(300 == eval->DurationFadeOn()); + CHECK(0 == eval->DurationOn()); + CHECK(0 == eval->DurationFadeOff()); + CHECK(100 == jled.MinBrightness()); + CHECK(200 == jled.MaxBrightness()); + } + SECTION("fade with from >= to") { + TestableJLed jled(1); + jled.Fade(200, 100, 300); + REQUIRE(dynamic_cast( + jled.brightness_eval_) != nullptr); + auto eval = dynamic_cast( + jled.brightness_eval_); + CHECK(0 == eval->DurationFadeOn()); + CHECK(0 == eval->DurationOn()); + CHECK(300 == eval->DurationFadeOff()); + CHECK(100 == jled.MinBrightness()); + CHECK(200 == jled.MaxBrightness()); + } + } + }; + TestableJLed::test(); +} TEST_CASE("UserFunc() allows to use a custom brightness evaluator", "[jled]") { class TestableJLed : public TestJLed { public: @@ -205,38 +253,6 @@ TEST_CASE("CandleBrightnessEvaluator simulated candle flickering", "[jled]") { CHECK(eval.Eval(999) > 0); } -TEST_CASE("FadeOnEvaluator evaluates to expected brightness curve", "[jled]") { - constexpr auto kPeriod = 2000; - - auto evalOn = FadeOnBrightnessEvaluator(kPeriod); - - CHECK(kPeriod == evalOn.Period()); - - const std::map test_values = { - {0, 0}, {500, 13}, {1000, 68}, {1500, 179}, - {1999, 255}, {2000, 255}, {10000, 255}}; - - for (const auto &x : test_values) { - CHECK(x.second == evalOn.Eval(x.first)); - } -} - -TEST_CASE("FadeOffEvaluator evaluates to expected brightness curve", "[jled]") { - constexpr auto kPeriod = 2000; - - // note: FadeOff is invervted FadeOn - auto evalOff = FadeOffBrightnessEvaluator(kPeriod); - - CHECK(kPeriod == evalOff.Period()); - const std::map test_values = { - {0, 0}, {500, 13}, {1000, 68}, {1500, 179}, - {1999, 255}, {2000, 255}, {10000, 255}}; - - for (const auto &x : test_values) { - CHECK(x.second == evalOff.Eval(kPeriod - x.first)); - } -} - TEST_CASE( "BreatheEvaluator evaluates to bell curve distributed brightness curve", "[jled]") {