Non-Linear Brightness

On the lookout for a maths type..

I’m about to do a blog item on PWM on the ESP8266 – the reason for that is – they’ve some new code which currently isn’t 100% or I’d be writing it up now – but suffice it to say the maximum duty cycle value is 22,700 or thereabouts. That just blows away what you can do with an Arduino.

Now, much as it would make for beautiful displays, that’s WAY too many steps – but as we know, our eyes are non-linear – so simply dividing that down to say a 256- entry lookup table would produce VERY rough stepping at low brilliance with far too smooth stepping at the top end.

I have the spreadsheet open, just need someone to jog my memory – what formula do we need to put in the cells to get 256 levels out of this?


9 thoughts on “Non-Linear Brightness

  1. My conclusion, based on about half a day of experiments, is that the “duty” parameter accepted by the PWM API in SDK 1.1.0 seems to be a number between 0.0 and 100.0 in Q8 format. So if you are used to thinking in levels between 0 and 255, the formula you’d use:
    new_duty_cycle_parameter = old_duty_cycle_parameter*100
    where old_duty_cycle is a number between 0 and 255
    As you noticed, high values, beyond 22500 or so, don’t work and you get get erratic wave forms. Also, the max frequency seems to be limited to between 10 and 16 kHz. i.e., the wave forms begin to look erratic at even lower duty cycles when you try with higher frequencies. I very much hope this is just a s/w bug they fix in the next SDK rather than a h/w limitation .

    1. Hi Hari – that original API is now dead…. up until 1.1.2, the open source code was 8 bits duty cycle – which is really no better than a cheap Chinese light flasher… the replacement which is sadly not open – takes us to a whole new level – I’m running 3 channels at 14 bit resolution – a 32 bit number… maximum duty value just over 22,000. The acid test is really low level – at value 1 you can ONLY see a LED in a dark room, level 2 is hardly discernible… by the time you get up near full brilliance, value differences of something like 1000 produce the same perceived change… I’m doing all my tests at 1Khz (1024) which is fine for lighting…

      See upcoming blog on the issue – I’ve some screen shots.

  2. Some good info here:

    Last year I made a simple PIC PWM dimmer for the LED cabin lighting and discovered firsthand that linear doesn’t cut it – too much change at the low end. almost no change for the last third ot the top. So it seems the correct curve would be exponential.

    As mentioned in the above link, some experimentation and hand-tweaking would be useful.I can envision trying to establish maybe 8 points that visually seem “equally-spaced” from dark to full, then trying to fit a simple curve to these points.

    Hey here’s something that purports to do the fitting:

    If I wasn’t so busy doing non-fun stuff, I’d dive into this ASAP, maybe redo my dimmer.

    1. Well J0hncc – therein lies the rub – there is no PWM.c – the PWM.h is part of the SDK and you need to add pwn to the makefile – it’s apparently no longer open source. Now I could see that for two possible reasons – (a) it’s not finished yet or (b) it’s so good for some reason they want to keep the source close. I’ve got it working tonight and there is no doubt it is potentially good – compare 8 channels at 14 bits (or that’s how it seems) to the pitiful offerings of, say, the Arduino – however my tests so far suggest that it’s not QUITE there – to be fair I only fired questions at them early evening and it is China so I don’t expect they’ll see them for a few hours yet. Give me another day and I’ll put up what I can. Right now I have LED control so fine that you simply cannot tell from once step to the other at the lowest brightness unless it is pitch black… More on this as I find out more.

  3. You are thinking about the CIE lab Formula?
    L = 116 * (Y/Yn)1/3 – 16 for Y/Yn > 0.008856
    L = 903.3 * Y/Yn otherwise

      1. Y lumiance, Yn reference luminance, L lightness
        You need to inverse the formula to calc the values:

        float lum=0;
        float destRange = 256; //256 value table
        float pwm = 4096-1; // pwm is 12 bits
        float inverseL = 0;
        for (float i = 0; i < destRange; i++)
        lum = (i / (destRange – 1)) * 100.0f;
        if (lum < 8.0)
        inverseL = lum / 903.3f;
        inverseL = (float)Math.Pow(((lum + 16.0) / 116.0), 3.0);
        Debug.WriteLine("table["+i + "]= "+(Math.Round(inverseL*pwm))+ " // "+( inverseL*100) + "%" );

        1. Aw thanks!!! I’ll give that a shot later. When done I’ll do a little writeup – just waiting for some feedback from Espressif as the SDK just will not do 100% duty cycle – still – it was version 1 of this..

Comments are closed.