jandelgado / jled

Non-blocking LED controlling library for Arduino and friends.
MIT License
324 stars 51 forks source link

Pulse LED? #84

Closed RossAWaddell closed 2 years ago

RossAWaddell commented 2 years ago

Question: is it possible to use the FadeOn() & FadeOff() functions with custom brightness levels to achieve a pulsing effect?

The documentation states the two fade functions only work with 0 (min) and 255 (max).

Here's the effect I'm aiming for (this is using a different library and the code is more complex; if possible, I'd like to get this down to just JLed).

EDIT: should I create a custom user function for this purpose? the user_func.ino example doesn't show how to use the custom class in the loop{}.

https://user-images.githubusercontent.com/29708900/145724551-2f620747-b040-44dc-a36d-728ee1ea7393.mp4

jandelgado commented 2 years ago

@RossAWaddell, out of the box this is not (yet) supported. But I'll think of adding a something like a minBrightness so the breathe effect can be used for that. In the meantime, you can use a user provided brightness evaluator:

// JLed user provided brightness function demo - Pulse effect.
// Copyright 2021 by Jan Delgado. All rights reserved.
// https://github.com/jandelgado/jled
#include <jled.h>

class Pulse : public jled::BrightnessEvaluator {
    uint16_t period_;
    uint8_t range_;

 public:
    Pulse() = delete;

    // period - duration of a full pulse period
    // range - brightness will be scaled to range [255, 255-range]
    Pulse(uint16_t period, uint8_t range) : period_(period), range_(range) {}

    uint8_t Eval(uint32_t t) const override {
        const auto half_period = Period() / 2;

        const auto val = (t < half_period)
                             ? jled::fadeon_func(t, half_period)
                             : jled::fadeon_func(Period() - t, half_period);
        return ((val * range_) >> 8) + (255 - range_);
    }
    uint16_t Period() const override { return period_; }
};

auto pulseEffect = Pulse(1000, 180);
auto led = JLed(2).UserFunc(&pulseEffect).Forever();

void setup() {}

void loop() { led.Update(); }

Play with the period and range values of the constructor to get the right effect.

RossAWaddell commented 2 years ago

Thank you! I've got it working but have a couple of wee questions:

  1. Is there a way to slow the fadeon_func? It seems like it just blinks on for the most part, or at least a really fast fade time. I tried using something other than half_period but it didn't work.
  2. Can I use something other than 255 in the range as the max?

BTW, I really appreciate your help. This project I'm working on is a multi-year labour of love that involves 5 different custom circuit boards I've designed and made and I really need to finish this before the end of 2022.

https://user-images.githubusercontent.com/29708900/145906029-a347db26-0c7f-4c3a-9bd8-cb07721304cc.mp4

// JLed user provided brightness function demo - Pulse effect.
// Copyright 2021 by Jan Delgado. All rights reserved.
// https://github.com/jandelgado/jled
#include <jled.h>

class Pulse : public jled::BrightnessEvaluator {
    uint16_t period_;
    uint8_t range_;

 public:
    Pulse() = delete;

    // period - duration of a full pulse period
    // range - brightness will be scaled to range [255, 255-range]
    Pulse(uint16_t period, uint8_t range) : period_(period), range_(range) {}

    uint8_t Eval(uint32_t t) const override {
        const auto half_period = Period() / 2;

        const auto val = (t < half_period)
                             ? jled::fadeon_func(t, half_period)
                             : jled::fadeon_func(Period() - t, half_period);
        return ((val * range_) >> 8) + (255 - range_);
    }
    uint16_t Period() const override { return period_; }
};

auto pulseEffect = Pulse(1500, 100);

auto led = JLed(10).UserFunc(&pulseEffect).DelayBefore(500).Forever();

void setup() {}

void loop() { 

  led.Update(); 

}
jandelgado commented 2 years ago

End of 2022? Then you have plenty of time left ;)

how about that? Pulse now takes period, min and max values as arguments. So you can control the minimum and maximum brightness. The "Speed" can be controlled by the period and/or by adding a DelayAfter, see below:

#include <jled.h>

// scale and lerp inspired from FastLED library.
uint8_t scale8(uint8_t x, uint8_t s) {
    return ((uint16_t)x * (uint16_t)s) >> 8;
}

// linear interpolation of t expressed as byte to the interval [a,b]
//   lerp8(a, b, 0)   = a
//   lerp8(a, b, 255) = b
uint8_t lerp8(uint8_t a, uint8_t b, uint8_t t) {
    if (b>a) {
        return a + scale8(b-a, t);
    } else {
        return a - scale8(a-b, t);
    }
}

class Pulse : public jled::BrightnessEvaluator {
    uint16_t period_;
    uint8_t min_, max_;

 public:
    Pulse() = delete;

    // period - duration of a full pulse period
    // range - brightness will be scaled to range [min,max]
    Pulse(uint16_t period, uint8_t min, uint8_t max) : period_(period), min_(min), max_(max) {}

    uint8_t Eval(uint32_t t) const override {
        const auto half_period = Period() / 2;

        const auto val = (t < half_period)
                             ? jled::fadeon_func(t, half_period)
                             : jled::fadeon_func(Period() - t, half_period);
    //    interpolate val to [min_, max_]
        return lerp8(min_, max_, val);
    }
    uint16_t Period() const override { return period_; }
};

// 1000 ms period = 500ms fade on + 500ms fade off. Min brightness = 10, Max brigthness = 150.
auto pulseEffect = Pulse(1000, 10, 150);
auto led = JLed(2).UserFunc(&pulseEffect).DelayAfter(500).Forever();

void setup() {}

void loop() { led.Update(); }
RossAWaddell commented 2 years ago

Noice! Is there a way to decouple the pulsing period (e.g. 1000 ms) from the initial fade on period (500ms)? I'd like to have a slow initial fade on (2-3 seconds) and then pulse at 500ms. Would I do that by initially setting up the led object with a FadeOn() and then destroying it/recreating it in the loop after the initial fade on?

jandelgado commented 2 years ago

You could use JLedSequence to play a sequence of LED effects, one after another. Try this and tweak for your needs:

#include <jled.h>

uint8_t scale8(uint8_t x, uint8_t s) {
    return ((uint16_t)x * (uint16_t)s) >> 8;
}

// linear interpolation of t expressed as byte to the interval [a,b]
//   lerp8(a, b, 0)   = a
//   lerp8(a, b, 255) = b
uint8_t lerp8(uint8_t a, uint8_t b, uint8_t t) {
    if (b > a) {
        return a + scale8(b - a, t);
    } else {
        return a - scale8(a - b, t);
    }
}

class Pulse : public jled::BrightnessEvaluator {
    uint16_t periodOn_, periodOff_;
    uint8_t min_, max_;

 public:
    Pulse() = delete;

    // periodOn - duration of a the fade on phase
    // periodOff - duration of a the fade off phase
    // range - brightness will be scaled to range [min,max]
    Pulse(uint16_t periodOn, uint16_t periodOff, uint8_t min, uint8_t max)
        : periodOn_(periodOn), periodOff_(periodOff), min_(min), max_(max) {}

    uint8_t Eval(uint32_t t) const override {
        const auto val = (t < periodOn_)
                             ? jled::fadeon_func(t, periodOn_)
                             : jled::fadeon_func(Period() - t, periodOff_);
        //  interpolate val to [min_, max_]
        return lerp8(min_, max_, val);
    }
    uint16_t Period() const override { return periodOn_ + periodOff_; }
};

constexpr auto kMinBrightness = 10;
constexpr auto kMaxBrightness = 150;
constexpr auto kPulseWidth = 500;
constexpr auto kDelayAfterPulse = 500;

auto firstPulseEffect = Pulse(0, kPulseWidth, kMinBrightness, kMaxBrightness);
auto endPulseEffect =
    Pulse(kPulseWidth, kPulseWidth, kMinBrightness, kMaxBrightness);

JLed leds[] = {
    JLed(2).FadeOn(3000).MaxBrightness(kMaxBrightness),
    JLed(2).UserFunc(&firstPulseEffect).DelayAfter(kDelayAfterPulse),
    JLed(2).UserFunc(&endPulseEffect).DelayAfter(kDelayAfterPulse).Forever(),
};

auto sequence = JLedSequence(JLedSequence::eMode::SEQUENCE, leds);

void setup() {}

void loop() { sequence.Update(); }
RossAWaddell commented 2 years ago

Perfect! I can't thank you enough!