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:
Realisierung per Software. Das Signal wird mittels Warteschleife oder ISR erzeugt.
Vorteile : Beliebige PortPins als Ausgabe möglich. Keine Begrenzung durch Anzahl der PWM-Einheiten
Nachteile : Zykluszeit kann zu lang sein. Hohe Rechenbelastung.
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.
Vorteile : Auch kurze Zykluszeiten realisierbar, Signalerzeugung geschieht im Hintergrund, Hauptprogramm wird nicht belastet
Nachteile : Festlegung auf bestimmte PortPins, Begrenzte Anzahl von Ausgängen
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
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..
Quellcode [zPWM_sw1.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;
} } }
/* Taktzahl: 1MHz/1500Hz = 667
Vorteiler: 667/256 = 2,6 -> 8 -> 2
OCR0A: 667/8 -1 = 82
Probe: (82+1)*8 = 664 -> 1506 Hz */
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 |= 2 ; // Vorteiler 1: /1; 2: /8; 3: /64;
TIMSK |= 1 << OCIE0A; // Timer/Counter0 OCMatch A I-Enable
OCR0A = 82 ; // 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< 15 ) 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 >= 14 ) level = 0 ;
else level++;
}
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..
Quellcode [zPWM_sw3.ino ]#include <avr/io.h> // Definitionen laden
#include <util/delay.h>
#define MAX_HELL 99 // 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;
} } }
/* Berechnung ISR-Werte
Aufruffrequenz: 9900 Hz -> 101 µs
Probe: (100+1)*1µs = 101 µs -> 9901 Hz
*/
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 |= 1 ; // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
TIMSK |= 1 << OCIE0A; // Timer/Counter0 Output Compare Match A Interrupt Enable
OCR0A = 100 ; // 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
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
}
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.
Maximale Helligkeit nun 128
ISR-Aufruffrequenz 12,8 kHz
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..
Quellcode [zPWM_sw4.ino ]#include <avr/io.h> // Definitionen laden
#include <util/delay.h>
#define MAX_HELL 128 // Maximaler Helligkeitwert
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;
} } }
/* Berechnung ISR-Werte
Aufruffrequenz: 12,8 kHz -> 78 µs
Probe: (77+1)*1µs = 78 µs -> 12821 Hz
*/
unsigned char helligkeit[ ] = { 0 , 0 , 0 , 0 } ; // Helligkeit der LED
unsigned char hellStufe[ ] = { 0 , 0 , 0 , 0 } ; // Helligkeitsstufe
unsigned char umrechnung[ ] = { 0 , 1 , 2 , 4 , 8 , 16 , 32 , 64 , 128 } ; // Helligkeitsstufen
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 |= 1 ; // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
TIMSK |= 1 << OCIE0A; // Timer/Counter0 O-Compare Match A Interrupt Enable
OCR0A = 77 ; // 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 ( hellStufe[ i] >= sizeof ( umrechnung) - 1 ) hellStufe[ i] = 0 ;
else hellStufe[ i] ++;
helligkeit[ i] = umrechnung[ hellStufe[ i] ] ;
}
else {
if ( up) {
if ( hellStufe[ 3 ] >= sizeof ( umrechnung) - 1 ) up= 0 ;
else hellStufe[ 3 ] ++;
}
else {
if ( hellStufe[ 3 ] <= 0 ) up= 1 ;
else hellStufe[ 3 ] --;
}
helligkeit[ 3 ] = umrechnung[ hellStufe[ 3 ] ] ;
_delay_ms( 5 ) ;
}
}
}
ISR( TIMER0_COMPA_vect) { // Interrupt Service Routine
static unsigned char level = 0 ; // zaehlt die Helligkeitslevel
asm volatile ( "sbi 0x12,0" ) ; // sbi PORTD,0
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
}