dim LEDs

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
jedie
Posts: 247
Joined: Fri Jan 29, 2016 12:32 pm
Contact:

dim LEDs

Post by jedie » Thu Dec 19, 2019 10:28 am

I would like to dim the power LED on my Sonoff devices, because it's very bright.
I created this:

Code: Select all

import machine
import utime


class Led:
    def __init__(self, name, pin, on, off):
        self.name = name
        self.pin = pin
        self._on = on
        self._off = off

    def on(self):
        self.pin.value(self._on)

    def off(self):
        self.pin.value(self._off)

    def toggle(self):
        if self.is_on:
            self.off()
        else:
            self.on()

    def flash(self, sleep=0.5, count=5):
        was_on = self.is_on
        for no in range(count):
            self.toggle()
            utime.sleep(sleep)
        if was_on:
            self.on()
        else:
            self.off()

    @property
    def is_on(self):
        return self.pin.value() == self._on

    @property
    def state(self):
        return 'ON' if self.is_on else 'OFF'

    def __str__(self):
        return '%s %s: %s' % (self.name, self.pin, self.state)


class PwmLed(Led):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.dim_duty = (None, 600, 800, 950)

        self.dim_max_level = len(self.dim_duty)  # Dimming down to the maximum
        self.current_dim_value = None  # Full ON

    def set_dim_level(self, dim_level):
        if not 0 <= dim_level <= self.dim_max_level:
            print(
                'ERROR: level %r is not between 0 and %i' % (dim_level, self.dim_max_level)
            )
            dim_level = self.dim_max_level
        self.current_dim_value = self.dim_duty[dim_level]

    def on(self):
        self.deinit_pwm()
        if self.current_dim_value is None:
            # turn full ON
            super().on()
        else:
            machine.PWM(self.pin, freq=100, duty=self.current_dim_value)

    def off(self):
        self.deinit_pwm()
        super().off()

    def deinit_pwm(self):
        machine.PWM(self.pin).deinit()


if __name__ == '__main__':
    relay = Led(name='relay', pin=machine.Pin(12, machine.Pin.OUT), on=1, off=0)
    power_led = PwmLed(name='power', pin=machine.Pin(13, machine.Pin.OUT), on=0, off=1)

    relay.on()
    utime.sleep(1)
    relay.off()

    for dim_level in range(power_led.dim_max_level):
        power_led.off()
        print(dim_level)
        power_led.set_dim_level(dim_level)
        power_led.on()
        utime.sleep(1)
        power_led.flash()
        utime.sleep(1)

    power_led.off()
The Problem is the

Code: Select all

power_led.flash()
... it works only for the max dim level. the lower dim levels has some flickering...
Any idea? Any better implementation?

User avatar
MostlyHarmless
Posts: 147
Joined: Thu Nov 21, 2019 6:25 pm
Location: Pennsylvania, USA

Re: dim LEDs

Post by MostlyHarmless » Thu Dec 19, 2019 5:50 pm

jedie wrote:
Thu Dec 19, 2019 10:28 am
... it works only for the max dim level. the lower dim levels has some flickering...
Any idea? Any better implementation?
The device has an ESP8266, right? On that chip PWM is emulated in software using an IRQ. That is why it has a very limited frequency and probably not the best stability either. Do you have an oscilloscope to verify?

nagylzs
Posts: 40
Joined: Sat Jan 19, 2019 8:01 pm

Re: dim LEDs

Post by nagylzs » Thu Dec 19, 2019 6:11 pm

I'm working on a LEDC driver for MicroPython and ESP32 that will be able to do fading from hardware, in the background. It is in early stages, but it might be usable next year. For esp8266, there is no such solution.

jedie
Posts: 247
Joined: Fri Jan 29, 2016 12:32 pm
Contact:

Re: dim LEDs

Post by jedie » Thu Dec 19, 2019 6:59 pm

Yes, i need this on ESP8266 ... If PWM is emulated in software does it then load the CPU and make everything slower?

I think i have a bug in the "is_on" check that result in flickering...

jedie
Posts: 247
Joined: Fri Jan 29, 2016 12:32 pm
Contact:

Re: dim LEDs

Post by jedie » Thu Dec 19, 2019 8:59 pm

I have now tried different variations. But it seems to me that machine.PWM() is buggy, isn't it?

e.g.: There are situations where I can't get the LED off. It always lights up with 100%. Hard reset changes nothing. I have to turn off the power.

EDIT: Ah! It seems that pwm.deinit() will not really reset.
Work-a-round is:

Code: Select all

pwm.freq(100)
pwm.duty(0)
pwm.deinit()

jedie
Posts: 247
Joined: Fri Jan 29, 2016 12:32 pm
Contact:

Re: dim LEDs

Post by jedie » Thu Dec 19, 2019 9:30 pm

This is now my working version:

Code: Select all

import machine
import utime


class Led:
    def __init__(self, name, pin, on, off):
        self.name = name
        self.pin = pin
        self._on = on
        self._off = off
        self.is_on = None
        self.off()

    def on(self):
        self.pin.value(self._on)
        self.is_on = True

    def off(self):
        self.pin.value(self._off)
        self.is_on = False

    def toggle(self):
        if self.is_on:
            self.off()
        else:
            self.on()

    def flash(self, sleep=0.5, count=6):
        was_on = self.is_on
        for no in range(count):
            self.toggle()
            utime.sleep(sleep)
        if was_on:
            self.on()
        else:
            self.off()

    @property
    def state(self):
        return 'ON' if self.is_on else 'OFF'

    def __str__(self):
        return '%s %s: %s' % (self.name, self.pin, self.state)


class PwmLed(Led):
    def __init__(self, *args, **kwargs):
        self.duty_values = (None, 700, 900, 1000)
        self.dim_max_level = len(self.duty_values)
        self.duty = None  # Full ON

        super().__init__(*args, **kwargs)

        self.pwm = machine.PWM(self.pin, freq=100, duty=0)

    def set_dim_level(self, dim_level):
        assert 0 <= dim_level <= self.dim_max_level, 'level %r is not between 0 and %i' % (
            dim_level, self.dim_max_level
        )

        was_on = self.is_on
        self.duty = self.duty_values[dim_level]
        if self.duty is None:
            self.off()
            self.deinit_pwm()
        if was_on:
            self.on()

    def on(self):
        if self.duty is not None:
            self.is_on = True
            self.pwm.duty(self.duty)
        else:
            super().on()

    def off(self):
        super().off()
        if self.duty is not None:
            self.deinit_pwm()

    def deinit_pwm(self):
        self.pwm.duty(0)
        self.pwm.freq(100)
        self.pwm.deinit()


if __name__ == '__main__':
    relay = Led(name='relay', pin=machine.Pin(12, machine.Pin.OUT), on=1, off=0)
    power_led = PwmLed(name='power', pin=machine.Pin(13, machine.Pin.OUT), on=0, off=1)

    for level in range(power_led.dim_max_level):
        print('\nset level to: %i' % level)
        power_led.set_dim_level(level)
        power_led.flash()

    power_led.off()

Post Reply