MezData-Logo

LEDs mit PWM dimmen

Helligkeit der LEDs mit Pulsweitenmodulation regeln

Dunkel
0 1 2 0
33%
0 1 2 0
66%
0 1 2 0
100%
0 1 2 0
Pulsbreiten der Helligkeiten

Das Prinzip wird hier am Beispiel von 4 Helligkeitsstufen verdeutlicht. Ein Zyklus dauert 3 Takte, die LED leuchtet von 0 Takte (gar nicht) bis 3 Takte (ständig). Die LEDs sollen mit 100Hz angesteuert werden, damit kein Flackern wahrnehmbar ist. Zwei Lösungsvarianten bieten sich an:

  1. Realisierung per Software. Das Signal wird mittels Warteschleife oder ISR erzeugt.
  2. Realisierung mit Hardware (Timer PWM System). Die µC habe bereits eine geeignete Hardware zur Erzeugung von PWM-Signalen eingebaut. An bestimmten Ausgängen können diese Signale abgegriffen werden.

PWM per Software mit ISR

L0 des Z-Eye @1MHz soll 4 Helligkeitsstufen {0..3} annehmen können und mit einer Frequenz von 100Hz angesteuert werden. In einer CTC-ISR des 8Bit Timer0 werden die Helligkeitslevel durchgezählt. Solange der gewünschte Helligkeitslevel nicht erreicht ist, ist die LED eingeschaltet. Bei 4 Werten sind 3 ISR-Aufrufe pro Zyklus nötig, die ISR wird 300 mal pro Sekunde aufgerufen werden (300Hz). Per Tasten an T0 (runter) und T1 (hoch) soll die Helligkeit eingestellt werden.

Quellcode [zPWM_sw.ino]
#include <avr/io.h>     // Definitionen laden 
#include <util/delay.h>
unsigned char keyEnter; // gedrueckte Tasten 
void keyCheck(){      // Tastaturabfrage mit Flankendedektion 
  static unsigned char keyOld = 0;   // alter Tasten-Zustand 
  unsigned char keyTest,tmp; 
  keyEnter = 0; keyTest = ~PIND >> 4;
  if (keyOld != keyTest){       // hat sich was getan 
    _delay_ms(10); tmp = (~PIND >> 4);// Prellen abwarten 
    if (tmp == keyTest){        // ist es stabil? 
      keyEnter = (~keyOld) & keyTest; // steigende Flanke
      keyOld = keyTest; 
} } } 
unsigned char helligkeit = 0; // Helligkeit der LED
int main(){    // Hauptprogramm 
  PORTB=0xe0;  // alle LED aus 
  DDRB= 0xff;  // PORTB als Ausgang
  PORTD=0x7f;  // PullUps an
  TCCR0A |= 1<<WGM01; // Timer im CTC-Mode
  TCCR0B |= 3;      // Vorteiler 1: /1; 2: /8; 3: /64;
  TIMSK |= 1<<OCIE0A; // Timer/Counter0 OCMatch A I-Enable
  OCR0A = 51; // Wert bei dem der Timer wieder auf 0 geht
  sei();            // globale Interruptfreigabe
  while(1){         // Endlosschleife 
    keyCheck(); // Maske,NLogik
    if((keyEnter&1) && helligkeit>0) helligkeit--; // T0?
    if((keyEnter&2) && helligkeit<3) helligkeit++; // T1?
  } 
}
ISR(TIMER0_COMPA_vect){ // Interrupt Service Routine
  static unsigned char level = 0; // zaehlt die Hell-Level
  if(level>=helligkeit) PORTB&=~1;// LED0 aus
  else PORTB|=1;                  // LED0 an
  if (level >= 2) level = 0;
  else level++;
}
Z-Eye Schema
Z-Eye Schema

Berechnung der ISR-Zeit

Taktzahl: 1MHz/300Hz = 3333
Vorteiler: 3333/256 = 13 -> 64 -> 3
OCR0A: 3333/64 -1 = 51
Probe: (51+1)*64 = 3328 ok

Mehr Helligkeitsstufen

L0 soll nun 16 Helligkeitsstufen {0..15} annehmen können.

Neben kleineren Änderungen muss die ISR nun mit 1500 Hz aufgerufen werden.

Modifizieren Sie die Software! Lösung anzeigen..

PWM mit Hardware

Quellcode [zPWM_hw.ino]
#include <avr/io.h>     // Definitionen laden 
#include <util/delay.h>
unsigned char keyEnter; //gedrueckte Tasten 
void keyCheck(){     // Tastaturabfrage mit Flankendedektion 
  static unsigned char keyOld = 0;   // alter Tasten-Zustand 
  unsigned char keyTest,tmp; 
  keyEnter = 0; keyTest = ~PIND >> 4;
  if (keyOld != keyTest){       // hat sich was getan 
    _delay_ms(10); tmp = (~PIND >> 4);// Prellen abwarten 
    if (tmp == keyTest){        // ist es stabil? 
      keyEnter = (~keyOld) & keyTest; // steigende Flanke
      keyOld = keyTest; 
} } } 
unsigned char helligkeit = 0; // Helligkeit der LED
int main(){    // Hauptprogramm 
  PORTB=0xe0;  // alle LED aus 
  DDRB= 0xff;  // PORTB als Ausgang
  PORTD=0x7f;  // PullUps an
  TCCR0A = 1<< COM0A1 | 1 << WGM01 | 1<< WGM00; // FastPWM
  TCCR0B = 1;       // Systemtakt durch 1
  sei();            // globale Interruptfreigabe
  while(1){         // Endlosschleife 
    keyCheck(); // Maske,NLogik
    if((keyEnter&1) && helligkeit>0) helligkeit--; // T0?
    if((keyEnter&2) && helligkeit<15) helligkeit++;// T1?
    OCR0A=helligkeit<<4; // Helligkeit*16
  } 
}

FastPWM-Mode

Timer0 wird im FastPWM-Mode betrieben.
Er läuft von 0 bis TOP = 0xff. Dabei wird Ausgang PB2 bei Timerwert 0 auf 1 und beim Erreichen von OC0A wieder auf 0 geschaltet.

Weil der Timer wird mit 1MHz Systemtakt betrieben wird dauert ein Zyklus 256 us.

Die Pulbreite kann mit OC0A in 256 Schritten eingestellt werden.

Die Taster verstellen den Helligkeitswert in 16er Schritten.

LED-Blöcke mit Software dimmen

Quellcode [zPWM_sw2.ino]
#include <avr/io.h>     // Definitionen laden 
#include <util/delay.h>
#define MAX_HELL 15 // Maximale Helligkeitsstufe
unsigned char keyEnter; // gedrueckte Tasten 
void keyCheck(){       // Tastaturabfrage mit Flankendedektion 
  static unsigned char keyOld = 0;     // alter Tasten-Zustand 
  unsigned char keyTest,tmp; 
  keyEnter = 0; keyTest = ~PIND >> 4;
  if (keyOld != keyTest){       // hat sich was getan 
    _delay_ms(10); tmp = (~PIND >> 4);// Prellen abwarten 
    if (tmp == keyTest){        // ist es stabil? 
      keyEnter = (~keyOld) & keyTest; // steigende Flanke
      keyOld = keyTest; 
} } } 
 
unsigned char helligkeit[] ={0,0,0,0}; // Helligkeit der LED
 
unsigned char auswerteTaste(){ // ermittelt die Nummer der niedrigsten Taste
  unsigned char i;
  for (i=0;i<3;i++){
    if(keyEnter&(1 << i)) break;
  }
  return i;
}
int main(){    // Hauptprogramm 
  PORTB=0xe0;  // alle LED aus 
  DDRB= 0xff;  // PORTB als Ausgang
  PORTD=0x7e;  // PullUps an
  DDRD=1;      // PD0 als Ausgang zum messen
  TCCR0A |= 1<<WGM01; // Timer im CTC-Mode
  TCCR0B |= 2;      // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
  TIMSK |= 1<<OCIE0A; // Timer/Counter0 Output Compare Match A Interrupt Enable
  OCR0A = 82;       // Wert bei dem der Timer wieder auf 0 gesetzt wird
  sei();            // globale Interruptfreigabe
  unsigned char i,up =1;
  while(1){         // Endlosschleife 
    keyCheck(); // Maske,NLogik
    if (keyEnter){
      i = auswerteTaste();
      if (helligkeit[i]>=MAX_HELL) helligkeit[i]=0;
      else helligkeit[i]++;
    }
    else{
      if(up){
        if (helligkeit[3]>=MAX_HELL) up=0;
        else helligkeit[3]++;
      }
      else{
        if (helligkeit[3]<=0) up=1;
        else helligkeit[3]--;
      }
      _delay_ms(50);
    }
  } 
}
ISR(TIMER0_COMPA_vect){ // Interrupt Service Routine
  static unsigned char level = 0; // zaehlt die Helligkeitslevel
  asm volatile ("sbi 0x12,0"); // sbi PORTD,0 = PD0 <- 1
  unsigned char i,ausgabe=0;
  for (i=0;i<4;i++){
    if(level>=helligkeit[i]) ausgabe |= 1<<i; // LEDs aus
  }
  PORTB = 0x0f | ausgabe<<4;
  if (level >= MAX_HELL-1) level = 0;
  else level++;
  asm volatile ("cbi 0x12,0"); // cbi PORTD,0 = PD0 <- 0
}

Die LED-Blöcke an PB7..PB4 sollen einstellbar in ihrer Helligkeit verändert werden können. Die Helligkeit von L0..L11 soll in Stufen von 0 bis 15 über die Taster T0..T2 eingestellt werden können. Der LED-Block L12..L15 gibt pulsierendes Licht ab. Die Steuerung der LED geschieht per Software in einer ISR.

  • Wiederholungsrate 100 Hz
  • Helligkeitstufen 0..15
  • ISR soll mit 1500 Hz aufgerufen werden
  • ISR-Abstand: 667 µs
  • Probe: (82+1)*8 µs = 664 µs -> 1506 Hz

Aufgaben

Erstellen Sie ein Struktogramm für auswerteTaste().

Implementieren und testen Sie die Lösung.

PD0 wird beim Eintritt in die ISR eingeschaltet und beim Ende ausgeschaltet. Messen Sie die Periodendauer und die Ausführungszeit der ISR.

Die Helligkeitsabstufungen sollen nun auf 100 (0..99) erhöht werden. Modifizieren und testen Sie den Quellcode entsprechend. Lösung anzeigen..

Der Wahrnehmung des Auges entsprechende Helligkeitsstufen

Wie bei 4.4 zu erkennen nimmt die Helligkeit bei kleinen Werten deutlich zu, bei grösseren Werten ist kaum ein Unterschied mehr zu erkennen. Das Auge hat keine lineare sondern eine logarithmische Empfindlichkeit. Die Lösung ist eine wahrnehmungsgerechte exponentielle Umrechnung der Helligkeitsstufen in Helligkeitswerte mittels Array.

unsigned char hellStufe[] ={0,0,0,0}; // Helligkeitsstufe
unsigned char umrechnung[] ={0,1,2,4,8,16,32,64,128}; // Helligkeitsstufen

Fügen Sie die beiden Felder ein und modifizieren und testen Sie das Programm. Lösung anzeigen..