2

I'm building a keyboard light with AVR micro controller.

There are two buttons, BRIGHT and DIM, and a white LED.

The LED isn't really linear, so I need to use a logarithmic scale (increase brightness faster in higher values, and use tiny steps in lower).

To do that, I adjust the delay between 1 is added or subtracted to/from the PWM compare match control register.

while (1) {
    if (btn_high() && OCR0A < 255) OCR0A += 1;
    if (btn_low() && OCR0A > 0) OCR0A -= 1;

    if (OCR0A < 25)
        _delay_ms(30);
    else if (OCR0A < 50)
        _delay_ms(25);
    else if (OCR0A < 128)
        _delay_ms(17);
    else
        _delay_ms(5);

}

It works nice, but there's a visible step when it goes from one speed to another. It'd be much better if the delay adjusted smoothly.

Is there some simple formula I can use? It must not contain division, modulo, sqrt, log or any other advanced math. I can use multiplication, add, sub, and bit operations. Also, I can't use float in it.

Or perhaps just some kind of lookup table? I'm not really happy with adding more branches to this if-else mess.

Martin Thorsen Ranang
  • 2,394
  • 1
  • 28
  • 43
MightyPork
  • 18,270
  • 10
  • 79
  • 133
  • Why can't you use log? Is it a performance issue? – Degustaf Dec 18 '14 at 21:37
  • ELSE if (OCR0A < 50) ? – willll Dec 18 '14 at 21:41
  • yeah good point, but that still isn't too smooth. @Degustaf it's a RISC 8-bit microcontroller, the program would be HUGE if I linked the math library. – MightyPork Dec 18 '14 at 21:43
  • Do you have a pulse width modulated output pin available? If so, reducing the number of possible levels and getting the log(level) by retrieving it from a lookup table would give a smooth transition and would get rid of the 30hz blinking annoyance you're seeing now. – Sniggerfardimungus Dec 18 '14 at 22:02
  • @Sniggerfardimungus: Based on the fact that an OCR is being used, I'd say that PWM is already in use. – Ignacio Vazquez-Abrams Dec 18 '14 at 22:13
  • I mistook the delays for his control of brightness as a function of the internally-represented, linear brightness. It's how he slows down the rate of increase and decrease. Yikes. This is a problem in search of a lookup table, nuff said. – Sniggerfardimungus Dec 18 '14 at 22:37

4 Answers4

2

The posted transfer function is quite linear. Suggest a linear delay calculation.

delay = 32 - OCR0A/8;

After accept edit

Various look-up-tables lend themselves to a close fit simple equations (constructed to avoid intermediate values > 65535) such as

 BRIGHTNESS_60 = (((index*index)>>2 + 128)*index)>>8;
Community
  • 1
  • 1
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • 2
    Of course `delay = 32 - OCR0A/8;` --> `delay = 32 - (OCR0A >> 3);` – chux - Reinstate Monica Dec 18 '14 at 22:42
  • Good point, but most compilers will spot a divide by 8 and use a shift in any case, but if it is necessary to change the slope to a non power of 2, the divide may be more maintainable. So on balance I'd stick with the explicit divide perhaps? – Clifford Dec 18 '14 at 23:22
  • @Clifford `x/8` and `x>>3` gives different results if `x` is an `int` and the value is negative, thus the compiler cannot always make the substitution your suggest. OTOH, if `x` is an unsigned type smaller than `int` (which is likely), then the compiler can substitute as it knows the value is never negative. Lastly, if `x` is `int` and the coder knows that its values are in the positive range, (but the compiler did not deduce that), the coder, using `x>>3` avoids calling a divide that `x/8` would do, which is expressly stated by OP as something to avoid. – chux - Reinstate Monica Dec 18 '14 at 23:41
  • Following @chux it's worth mentioning that if you need to divide but cannot, you can approximate a division with a multiply and a shift. Example 1/7 ~ 36/256 so multiply by 36 then 8 shifts (or drop the ls byte in assembler). – Weather Vane Dec 19 '14 at 16:58
1

The scaling isn't quite logarithmic so simply using log() isn't enough.

I have tackled this problem in the past by using a LUT with 18 entries and going an entire step at a time (i.e. the control variable varies from 0 to 17 and then is shoved through the LUT), but if finer control is required then having 52 or more is certainly doable. Make sure to put it in flash so that it doesn't consume any SRAM though.


Edit by MightyPork
Here's arrays I used in the end - obtained from the original array by linear interpolation.

Basic

#define BRIGHTNESS_LEN 60
const uint8_t BRIGHTNESS[] PROGMEM = {
    0, 1, 1, 2, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9,
    10, 11, 13, 14, 16, 18, 21, 24, 27, 30, 32,
    35, 38, 40, 42, 45, 48, 50, 54, 58, 61, 65,
    69, 72, 76, 80, 85, 90, 95, 100, 106, 112,
    119, 125, 134, 142, 151, 160, 170, 180, 190,
    200, 214, 228, 241, 255
};

Smoother

#define BRIGHTNESS_LEN 121
const uint8_t BRIGHTNESS[] PROGMEM = {
    0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5,
    6, 6, 6, 7, 7, 8, 8, 8, 9, 10, 10, 10, 11, 12, 13, 14, 14,
    15, 16, 17, 18, 20, 21, 22, 24, 26, 27, 28, 30, 31, 32, 34,
    35, 36, 38, 39, 40, 41, 42, 44, 45, 46, 48, 49, 50, 52, 54,
    56, 58, 59, 61, 63, 65, 67, 69, 71, 72, 74, 76, 78, 80, 82,
    85, 88, 90, 92, 95, 98, 100, 103, 106, 109, 112, 116, 119,
    122, 125, 129, 134, 138, 142, 147, 151, 156, 160, 165, 170,
    175, 180, 185, 190, 195, 200, 207, 214, 221, 228, 234, 241,
    248, 255
};
Community
  • 1
  • 1
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
0

It sounds like you really want to use some linear function of a logarithm, but without the overhead of the floating point math library. A crude fixed point logarithm can be coded as

uint_8 log2fix(uint_8 in)
{
    if(in == 0)
        return 0;
    uint_8 out = 0;

    while(in > 0)
    {
        in = in >> 1;
        out++;
    }

    return out - 1;
}

This will give you a rough approximation. If you want more precision there is a fast fixed point algorithm that you should be able to modify for Q8.0 to Q3.5.

Community
  • 1
  • 1
Degustaf
  • 2,655
  • 2
  • 16
  • 27
0

You have over-complicated the issue. You have already turned the logarithmic problem into a linear one by defining a variable update rate rather than a variable PWM step - so you have essentially solved the problem, but not seen the simple arithmetic relationship.

If you take the OCR0A vs delay points you have selected (25,30), (50,25), (128,17), it can be seen that that is an approximately linear relationship described by (approximately) y = 0.125x + 32, which can be rearranged as y = 32 - x / 8

enter image description here

So what you need is:

while (1) 
{
    if (btn_high() && OCR0A < 255) OCR0A += 1;
    if (btn_low() && OCR0A > 0) OCR0A -= 1;

    _delay_ms( 32 - OCR0A / 8 ) ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165