www.mikrocontroller.net

LED-Fading

Inhaltsverzeichnis

[bearbeiten] Das Problem

Die Aufgabe klingt eigentlich recht einfach. Eine LED soll mittels PWM in ihrer Helligkeit gesteuert werden. Und weils so schön ist, möchte man sie geheimnisvoll aufleuchten lassen, sprich langsam heller und dunkler werden lassen. Der Fachmann nennt das Fading. Das Problem zeigt sich allerdings recht schnell. Wenn man eine 8-Bit PWM linear zwischen 0..255 laufen lässt, dann scheint die LED nicht linear gedimmt zu werden. Sie wird relativ schnell hell und bleibt lange hell.

[bearbeiten] Die Erklärung

Des Rätsels Lösung liegt in der Kennline des menschlichen Auges. Diese ist nichtlinear, genauer gesagt: sie ist nahezu logarithmisch. Das ermöglicht die Wahrnehmung eines sehr großen Helligkeitsbereichs, angefangen von Vollmond mit ~1/4 Lux über eine normale Schreibtischbeleuchtung mit ca. 750 Lux bis zu einem hellen Sommertag mit bis zu 100.000 Lux. Solche hochdynamischen Signale sind nur mit einer logarithmischen Kennline in den Griff zu kriegen, auch von Mutter Natur.

[bearbeiten] Die Kennlinie des Auges genau betrachtet

Die Kennlinie das menschlichen Auges ist annähernd logarithmisch. Das wurde vor langer Zeit duch das Weber-Fechner-Gesetz beschrieben. Genauere Untersuchungen zur Gammakorrektur führten jedoch zur Stevenschen Potenzfunktion. Diese beschreibt das menschliche Auge etwas besser. Die Unterschiede sind jedoch marginal. Vorsicht! Der Artikel Gamma-Korrektur in Wikipedia verweist fälschlicherweise auf das Weber-Fechner Gesetz.

Praktisch heißt das, daß wir unserem Auge große physikalische Helligkeitsunterschiede präsentieren müssen, damit es das als lineare Helligkeitsteigerung erkennt. Etwas wissenschaftlicher formuliert heißt das, wir müssen durch Verkettung der logarithmischen Kennline des Auges mit einer exponentiellen Kennline eine psychiologisch lineare Helligkeitswirkung erzielen.

[bearbeiten] Das Demoprogramm

Das wird in dem unten gezeigten Beispielprogramm gemacht. Dabei wird die Wirkung verschiedener PWM-Auflösungen demonstriert. Eine 8-Bit PWM wird mit 4/8/16 und 32 nichtlinearen Stufen betrieben, welche über eine Exponentialfunktion berechnet wurden. Dazu dient die Exceltabelle. Die einzelnen benachbarten Werte haben zueinander ein konstantes Verhältnis, das in der Exceltabelle als 'Factor' berechnet wird. Ausserdem werden eine 10-Bit PWM mit 64 Stufen sowie, als die Königsklasse, eine 16-Bit PWM mit 256 Stufen betrieben.

Das Programm ist urprünglich auf einem AVR vom Typ ATmega32 entwickelt und getestet. Aber es ist leicht auf jeden AVR portierbar, welcher eine PWM zur Verfügung hat. Der AVR muss mit etwa 8 MHz getaktet werden, egal ob mit internem Oszillator oder von aussen mit Quarz. Man muss nur noch eine LED mittels Vorwiderstand von ca. 1 kOhm an Pin PD5 anschliessen und los gehts. Es sollte hier noch erwähnt werden, dass das Programm mit eingeschalteter Optimierung compiliert werden muss, sonst stimmen die Zeiten der Warteschleifen nicht.

Bei Verwendung der LEDs auf dem STK500 bzw. bei der Verwendung von invertierenden Treiberstufen ist das "#define STK500 false" durch "#define STK500 true" zu ersetzen.

Das Programm durchläuft alle 6 PWMs und lässt dabei die LED jeweils 3 mal glimmen. Mit 4 Schritten Auflösung ist das natürlich ruckelig, mit 8 schon wesentlich besser. Mit 16 Stufen sieht man bei langsamen Änderungen noch Stufen, dreht man die Ein- und Ausblendzeiten runter ist der Übergang schon recht flüssig. Die 8-Bit PWM mit 32 Stufen unterscheidet sich praktisch nicht von der 10-Bit PWM mit 64 Stufen, es sei denn, man macht extrem langsame Einblendungen. Hier schlägt die Stunde der 16-Bit PWM. Diese wird bewußt sehr langsam ausgeführt um zu demonstrieren, daß hiermit praktisch keine Stufen mehr sichtbar sind, egal wie langsam gedimmt wird. Wie man auch sieht sind die drei höherauflösenden PWMs im unteren Bereich an ihrer Auflösungsgrenze, da einige PWM-Werte mehrfach vorkommen. Da heißt gleichzeitig, daß eine Steigerung der Stufenanzahl relativ sinnlos ist.

//*****************************************************************************
//*
//*  LED fading test
//*  uses exponential PWM settings to achive visual linear brightness
//*
//*  ATmega32 @ 8 MHz
//*  
//*                  
//*****************************************************************************
 
#define F_CPU 8000000L
 
#define true 1
#define false 0
 
#define STK500 false
 
#include <inttypes.h>
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
 
// global variables
 
uint16_t pwmtable_8A[4]   PROGMEM = {0, 16, 64, 255};
uint16_t pwmtable_8B[8]   PROGMEM = {0, 4,  8, 16, 32, 64,  128, 255};
uint16_t pwmtable_8C[16]  PROGMEM = {0, 2, 3, 4, 6, 8, 11, 16, 23, 32, 45, 64,
                                    90, 128, 181, 255};
uint16_t pwmtable_8D[32]  PROGMEM = {0, 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 8, 10, 11,
                                    13, 16, 19, 23, 27, 32, 38, 45, 54, 64, 76,
                                    91, 108, 128, 152, 181, 215, 255};
 
uint16_t pwmtable_10[64]  PROGMEM = {0, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5,
                                    5, 6, 6, 7, 8, 9, 10, 11, 12, 13, 15, 17,
                                    19, 21, 23, 26, 29, 32, 36, 40, 44, 49, 55,
                                    61, 68, 76, 85, 94, 105, 117, 131, 146, 162,
                                    181, 202, 225, 250, 279, 311, 346, 386, 430,
                                    479, 534, 595, 663, 739, 824, 918, 1023};
 
uint16_t pwmtable_16[256] PROGMEM = {0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2,
                                     2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3,
                                     4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6,
                                     6, 7, 7, 7, 8, 8, 8, 9, 9, 10, 10, 10, 11,
                                     11, 12, 12, 13, 13, 14, 15, 15, 16, 17, 17,
                                     18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
                                     29, 31, 32, 33, 35, 36, 38, 40, 41, 43, 45,
                                     47, 49, 52, 54, 56, 59, 61, 64, 67, 70, 73,
                                     76, 79, 83, 87, 91, 95, 99, 103, 108, 112,
                                     117, 123, 128, 134, 140, 146, 152, 159, 166,
                                     173, 181, 189, 197, 206, 215, 225, 235, 245,
                                     256, 267, 279, 292, 304, 318, 332, 347, 362,
                                     378, 395, 412, 431, 450, 470, 490, 512, 535,
                                     558, 583, 609, 636, 664, 693, 724, 756, 790,
                                     825, 861, 899, 939, 981, 1024, 1069, 1117,
                                     1166, 1218, 1272, 1328, 1387, 1448, 1512,
                                     1579, 1649, 1722, 1798, 1878, 1961, 2048,
                                     2139, 2233, 2332, 2435, 2543, 2656, 2773,
                                     2896, 3025, 3158, 3298, 3444, 3597, 3756,
                                     3922, 4096, 4277, 4467, 4664, 4871, 5087,
                                     5312, 5547, 5793, 6049, 6317, 6596, 6889,
                                     7194, 7512, 7845, 8192, 8555, 8933, 9329,
                                     9742, 10173, 10624, 11094, 11585, 12098,
                                     12634, 13193, 13777, 14387, 15024, 15689,
                                     16384, 17109, 17867, 18658, 19484, 20346,
                                     21247, 22188, 23170, 24196, 25267, 26386,
                                     27554, 28774, 30048, 31378, 32768, 34218,
                                     35733, 37315, 38967, 40693, 42494, 44376,
                                     46340, 48392, 50534, 52772, 55108, 57548,
                                     60096, 62757, 65535};
 
// long delays
 
void my_delay(uint16_t milliseconds) {
    for(; milliseconds>0; milliseconds--) _delay_ms(1);
}
 
// 8-Bit PWM with only 4 different settings
 
void pwm_8_4(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM
#else
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM
#endif
    TCCR1B = 0x08;
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 4;            // precaler 256 -> ~122 Hz PWM frequency
 
    for(tmp=0; tmp<=3; tmp++){
      OCR1A =  pgm_read_word(pwmtable_8A+tmp);
      my_delay(delay);
    }
 
    for(tmp=3; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_8A+tmp);
      my_delay(delay);
    }
}
 
// 8-Bit PWM with 8 different settings
 
void pwm_8_8(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM
#else
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM
#endif
    TCCR1B = 0x08;
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 4;            // precaler 256 -> ~122 Hz PWM frequency
 
    for(tmp=0; tmp<=7; tmp++){
      OCR1A = pgm_read_word(pwmtable_8B+tmp);
      my_delay(delay);
    };
 
    for(tmp=7; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_8B+tmp);
      my_delay(delay);
    }
}
 
// 8-Bit PWM with 16 different settings
 
void pwm_8_16(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM
#else
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM
#endif
    TCCR1B = 0x08;
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 4;            // precaler 256 -> ~122 Hz PWM frequency
 
    for(tmp=0; tmp<=15; tmp++){
      OCR1A = pgm_read_word(pwmtable_8C+tmp);
      my_delay(delay);
    }
 
    for(tmp=15; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_8C+tmp);
      my_delay(delay);
    }
}
 
// 8-Bit PWM with 32 different settings
 
void pwm_8_32(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC1;          // inverted PWM on OC1A, 8 Bit Fast PWM
#else
    TCCR1A = 0x81;          // non-inverted PWM on OC1A, 8 Bit Fast PWM
#endif
    TCCR1B = 0x08;
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 4;            // precaler 256 -> ~122 Hz PWM frequency 
 
    for(tmp=0; tmp<=31; tmp++){
      OCR1A = pgm_read_word(pwmtable_8D+tmp);
      my_delay(delay);
    }
 
    for(tmp=31; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_8D+tmp);
      my_delay(delay);
    }
}
 
// 10-Bit PWM with 64 different settings
 
void pwm_10_64(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC3;          // inverted PWM on OC1A, 10 Bit Fast PWM
#else
    TCCR1A = 0x83;          // non-inverted PWM on OC1A, 10 Bit Fast PWM
#endif
    TCCR1B = 0x08;
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 3;            // precaler 64 -> ~122 Hz PWM frequency
 
    for(tmp=0; tmp<=63; tmp++){
      OCR1A = pgm_read_word(pwmtable_10+tmp);
      my_delay(delay);
    }
 
    for(tmp=63; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_10+tmp);
      my_delay(delay);
    }
}
 
// 16-Bit PWM with 256 different settings
 
void pwm_16_256(uint16_t delay){
 
    int16_t tmp;
 
#if STK500
    TCCR1A = 0xC2;          // inverted PWM on OC1A, 16 Bit Fast PWM
#else
    TCCR1A = 0x82;          // non-inverted PWM on OC1A, 16 Bit Fast PWM
#endif
    TCCR1B = 0x18;
    ICR1 = 0xFFFF;          // TOP for PWM, full 16 Bit
 
    TCCR1B &= ~0x7;         // clear clk setting
    TCCR1B |= 1;            // precaler 1 -> ~122 Hz PWM frequency
 
    for(tmp=0; tmp<=255; tmp++){
      OCR1A = pgm_read_word(pwmtable_16+tmp);
      my_delay(delay);
    }
 
    for(tmp=255; tmp>=0; tmp--){
      OCR1A = pgm_read_word(pwmtable_16+tmp);
      my_delay(delay);
    }
}
 
int main(void)
{
    int16_t i;
    int16_t step_time=400;      // delay in millisecond for one fading step
 
    DDRD |= (1<<PD5);           // LED uses OC1A
 
    // test all fading routines
 
    while(1) {
 
      for(i=0; i<3; i++) pwm_8_4(step_time);
      my_delay(1000);
      for(i=0; i<3; i++) pwm_8_8(step_time/2);
      my_delay(1000);    
      for(i=0; i<3; i++) pwm_8_16(step_time/4);
      my_delay(1000);
      for(i=0; i<3; i++) pwm_8_32(step_time/8);
      my_delay(1000);
      for(i=0; i<3; i++) pwm_10_64(step_time/16);
      my_delay(1000);
      for(i=0; i<3; i++) pwm_16_256(step_time/16);
      my_delay(1000);
 
    };
 
    return 0;
}
webmaster@mikrocontroller.netImpressumWerbung auf Mikrocontroller.net