MezData-Logo Creative Commons License 528 :AVR: LEDs mit PWM dimmen

Schlüsselwörter: LED dimmen, PWM, Timer, Interrupt, AVR, Assembler, C

Helligkeit der LEDs mit Pulsweitenmodulation regeln

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).

0 1 2 0 1 2 0 1 2 0 1 2 0
Dunkel 33% 66% 100%

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

Eine LED an PB0 eines ATtiny 2313 @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 PD0(runter) und PD1(hoch) soll die Helligkeit eingestellt werden (checkKeys.h verwenden).

Erstellen Sie eine Lösung! Lösung anzeigen..

Die LED an PB0 soll nun 16 Helligkeitsstufen {0..15} annehmen können, ATtiny 2313 @1MHz, Timer0 CTC-Interrupt .

Erstellen Sie eine Lösung! Lösung anzeigen..

PWM mit Hardware

 

Quellcode [LED_PWM2.c]
#include <avr/io.h>     // Definitionen laden 
#include <util/delay.h> // CPU Frequenz einstellen! 
#include <avr/interrupt.h>

#define KEYREADER ~PIND & 0b11 // Einlesen, invertieren, maskieren zurecht schieben 
#include "keycheck.inc"        // http://mezdata.de/avr/083_c-entprellen/keycheck.inc

unsigned char helligkeit = 0; // Helligkeit der LED

/*
Timer0 Ausgang OC0A(PB2) Timer laeuft im FastPWM-Mode mit TOP = 0xff, Vorteiler 1
Zykluszeit ist 256 us Pulbreite kann in 256 Schritten eingestellt werden. 
*/

int main(){         // Hauptprogramm 
  PORTB=0xff;       // alle LED aus 
  DDRB=0xff;        // PB als Ausgang
  PORTD=0xff;       // PullUps an
  TCCR0A = 1<< COM0A1 | 1<< COM0A0 | 1 << WGM01 | 1<< WGM00;
  TCCR0B = 1;       // Systemtakt durch 1
  while(1){         // Endlosschleife 
    keyCheck();
    if(keyEnter&1){ // Taste0 gedrueckt?
      if(helligkeit>0)helligkeit--;
    }
    if(keyEnter&2){ // Taste1 gedrueckt?
      if(helligkeit<15)helligkeit++;
    }
    OCR0A=helligkeit<<4;
  } 
  return 0; 
}

7LED mit Software dimmen

MIttels eines ATtiny2313 @ 1MHz sollen auf dem STK200 7 LED einstellbar in ihrer Helligkeit verändert werden können. Die LED an PB0..PB6 leuchten bei log. 0. Die Helligkeit der ersten 6 LED soll in Stufen von 0 bis 15 über die Taster PD0..PD5 eingestellt werden können. Die 7. LED gibt pulsierendes Licht ab. Da hier 7 LEDs angesteuert werden sollen, reicht der PWM-Ausgang des ATiny2313 nicht aus, die Steuerung der LED geschieht per Software in einer ISR. Der bereits vorhandene Code soll vervollständigt und weiter entwickelt werden:

Quellcode [6LED_mit_PWM_vorgabe.c]
#include  // Definitionen laden
#include  // Interrupts laden
#include  // Delay-Bibliothek laden

// **  mit STK 200 (LED neg. Logik) und ATtiny2313 @ 1MHz

/* 6 LEDs sollen in ihrer Helligkeit gesteuert werden
   Helligkeit soll Werte von 0 bis 15 annehmen
   Wiederholung 100 mal pro Sekunde: ISR muss 1500 * pro Sekunde aufgerufen werden
*/

unsigned char helligkeit[]={7,15,14,0,1,2,3}; // Helligkeitswerte der LED

ISR(TIMER0_OVF_vect){
  static unsigned char level = 0;           // zaehlt die Helligkeitslevel
  unsigned char i,ausgabe=0;
  TCNT0= ?;                                 // wieder richtig vorspannen
  for (i=0;i<7;i++){
    if(level>=helligkeit[i]) ausgabe |= 1 << i; // LED aus
  }
  PORTB = (PORTB & 0b10000000) | ausgabe;
  if (level >= 14) level = 0;
  else level++;
}

#define KEYREADER ~PIND & 0b00111111 // Einlesen, invertieren, maskieren zurecht schieben  
#include "keycheck.inc"        // http://mezdata.de/avr/083_c-entprellen/keycheck.inc 

int auswerteTaste(){ // ermittelt die Nummer der niedrigsten Taste
  unsigned char i;
  for (i=0;i<6;i++){
    if(keyEnter&(1 << i)) break;
  }
  return i;
}

int main(){          // Hauptprogramm
  unsigned char i,up =1;
  PORTB=0xff;        // alle LED aus
  DDRB=0xff;         // PB als Ausgang
  TCCR0B = ?;        // Phi / ?
  TIMSK = 1<< TOIE0; // Interrupt frei geben
  sei();             // globale Interruptfreigabe
  while(1){          // Endlosschleife
    keyCheck();
    if (keyEnter){
      i = auswerteTaste();
      if (helligkeit[i]>=15) helligkeit[i]=0;
      else helligkeit[i]++;
    }
    else{
      if(up){
        if (helligkeit[6]>=15) up=0;
        else helligkeit[6]++;
      }
      else{
        if (helligkeit[6]<=0) up=1;
        else helligkeit[6]--;
      }
      _delay_ms(50);
    }
  }
  return 0;
}

Ermitteln sie die richtigen Einstellungen des ISR: Vorteiler, Vorspannen. Wie genau sind Sie rechnerisch am Ideal?

Erstellen Sie ein Struktogramm für auswerteTaste().

Implementieren und testen Sie die Lösung.

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

Begutachten Sie den vom Compiler erzeugten Assembler-Code für die ISR.

Die Helligkeitsabstufungen sollen nun auf 100 erhöht werden, die ISR müsste dafür alle 100µs aufgerufen werden. Bislang braucht die ISR bis zu 232µs für ihre Ausführung. Neben der Erhöhung des Systemtaktes gibt es die Möglichkeiten das Programm in Assembler zu schreiben oder zu versuchen es in C zu optimieren.

Versuchen Sie das Programm in C zu optimieren und messen Sie die Zeiten. Lösung anzeigen..

Übersetzen Sie die ISR und die notwendingen Bestandteile zum Testen der Ausführungszeit in Assembler. Messen Sie die Ausführungszeit der ISR. Lösung anzeigen..

Anschluß einer RGB-LED an PB0..PB2 gegen GND

R D0 1 2 D1 G
B D2 3 4 D3
  D4 5 6 D5
D6 7 8 D7
GND GND 9 10 VCC  
ATtiny2313 STK200 I/O Port B

Nach Anschluß einer RGB-LED wurde der Programmcode erweitert:

Quellcode [6LED_mit_PWM2.c]
// *** LED Helligkeitssteuerung mit PWM 2.0 (c) Oliver Mezger 29.6.2014

#include <avr/io.h>        // Definitionen laden
#include <avr/interrupt.h> // Interrupts laden
#include <util/delay.h>    // Delay-Bibliothek laden
#include <avr/pgmspace.h>  // Flashzugriffe laden

// **  mit STK 200 (LED neg. Logik) und ATtiny2313 @ 4MHz

/* 6 LEDs sollen in ihrer Helligkeit gesteuert werden
   Helligkeit soll Werte von 0 bis 255 annehmen
   Wiederholung 100 mal pro Sekunde: ISR muss 25500 * pro Sekunde aufgerufen werden
   Soll-Zeit: 4000000us/25500 = 39,2us
   Vorteiler 4*39,2/256 = 0,6 gewaehlt: 1
   TOP-Wert des Timers 4*39,2 = 156,8 gewaehlt 156
   Tatsaechliche Zeit zwischen ISR-Aufruf: 0,25us*156= 39us
*/
unsigned char helligkeit[]={127,255,254,0,1,2,3}; // Helligkeitswerte der LED

ISR(TIMER0_COMPA_vect){ // bei 4MHZ Aufruf alle 39us Dauer 24,2us
  static unsigned char level = 0; // zaehlt die Helligkeitslevel
  asm volatile ("sbi 0x16,7"); // sbi PINB,7 zum Messen der ISR Dauer
  unsigned char i,ausgabe=0;
  unsigned char k=1;
  for (i=0;i<7;i++){
    if(level>=helligkeit[i]) ausgabe |= k; // LED aus
    k=k<<1;
  }
  level++;
  PORTB = (PORTB & 0b10000000) | ausgabe;
  asm volatile ("sbi 0x16,7"); // sbi PINB,7
}

#define KEYREADER ~PIND & 0b00111111 // Einlesen, invertieren, maskieren zurecht schieben  
#include "keycheck.inc"        // http://mezdata.de/avr/083_c-entprellen/keycheck.inc 

int auswerteTaste(){ // ermittelt die niedrigste Taste
  unsigned char i;
  for (i=0;i<6;i++){
    if(keyEnter&(1<<i)) break;
  }
  return i;
}
// Helligkeitswerte dem Empfinden anpassen
const unsigned char PROGMEM anpassung[]={0,1,2,3,4,5,7,8,10,12,14,16,20,23,28,33,39,46,55,65,77,91,108,128,151,179,212,255};

void pulsierendWeis(){
  static unsigned char up=1,move=0;
  if(up){
    move++;
    if (move>=sizeof(anpassung)-1) up=0;
  }
  else{
    move--;
    if (move<=0) up=1;  
  }
  helligkeit[0]=helligkeit[1]=helligkeit[2]=pgm_read_byte(&anpassung[move]);
}

typedef enum {Rup,Rdown,Gup,Gdown,Bup,Bdown}fadeRichtung;
const fadeRichtung fadeIt[]={Rup,Gup,Rdown,Bup,Gdown,Rup,Gup,Rdown,Gdown,Bdown};
void rgbFade(){  // Code ist fehlerhaft!
  static unsigned char fade=0;
  static unsigned char moveR=0,moveG=0,moveB=0;
  switch (fadeIt[fade]){
    case Rup:
      moveR++;
      if (moveR>=sizeof(anpassung)-1) fade++;
      helligkeit[0]=pgm_read_byte(&anpassung[moveR]);
      break;
    case Rdown:
      moveR--;
      if (moveR<=0) fade++;
      helligkeit[0]=pgm_read_byte(&anpassung[moveR]);
      break;
    case Gup:
      moveG++;
      if (moveG>=sizeof(anpassung)-1) fade++;
      helligkeit[1]=pgm_read_byte(&anpassung[moveG]);
      break;
    case Gdown:
      moveG--;
      if (moveG<=0) fade++;
      helligkeit[1]=pgm_read_byte(&anpassung[moveG]);
      break;
    case Bup:
      moveB++;
      if (moveB>=sizeof(anpassung)-1) fade++;
      helligkeit[2]=pgm_read_byte(&anpassung[moveB]);
      break;
    case Bdown:
      moveB--;
      if (moveB<=0) fade++;
      helligkeit[2]=pgm_read_byte(&anpassung[moveB]);
      break;
  }
  if (fade>=sizeof(fadeIt)) fade=0;
}
int main(){         // Hauptprogramm
  unsigned char i;
  PORTB=0xff;       // alle LED aus
  DDRB=0xff;        // PB als Ausgang
  TCCR0B = 1;       // Phi
  TCCR0A= 1<<WGM01; // CTC Mode mit OCR0A als TOP
  TIMSK = 1<<OCIE0A;// Interrupt frei geben
  OCR0A=156;         // bei 82 Interrupt ausloesen
  sei();            // globale Interruptfreigabe
  while(1){         // Endlosschleife
    keyCheck();
    if (keyEnter){
      i = auswerteTaste();
      if (helligkeit[i]>=255) helligkeit[i]=0;
      else helligkeit[i]++;
    }
    else{
      rgbFade();
      //pulsierendWeis();
      _delay_ms(5);
    }
  }
  return 0;
}