| Mikrocontroller | Gesamtanzahl realisierbarer 16-bit-PWM-Kanäle |
|---|---|
| ATtiny24, ATtiny44, ATtiny84, ATtiny2313 | 4 (2+2) |
| ATtiny25, ATtiny45, ATtiny85 | 4 (0+2+2) |
| ATtiny13 | 2 (0+2) |
| ATtiny261, ATtiny461, ATtiny861 | 4 (0+4) |
| ATtiny12, ATtiny26 | 0 |
| ATmega48, ATmega88, ATmega168 | 6 (2+2+2) |
| ATmega8 | 3 (2+1) |
| ATmega16, ATmega32 | 4 (2+1+1) |
| ATmega64 | 8 (6+2) |
| AVR |
|---|
| LED-PWM.zip [Einsicht] |
.nolist .include "m8def.inc" .include "makros.i90" .listDer Quelltext ist für avrasm2 von Atmel, nicht für avr-gcc. Die selbst erstellte Makrosammlung „makros.i90“ erspart Tipparbeit.
// Port B // PB0 (14) SLEEP-Anzeige // PB1 OC1A (15) // PB2 OC1B (16) PWM-Ausgänge für LEDs // PB3 OC2 (17)Hier sind die 3 Leuchtdioden am ATmega8 (DIL, Durchsteck) anzuschließen. Mit Vorwiderstand nach Masse — oder über einen Strom verstärkenden Transistor.
// Register R8..R15 sowie R23 für ISR erforderlich .def ZERO =r8 // immer 0x00 .def ONES =r9 // immer 0xFF .def LEN2L =r10 // gepuffert (Pulslänge, von außen zu setzen) .def LEN2H =r11 .def isrt1 =r12 // ISR-Register (SREG-Backup) .def TCNT2H =r13 // High-Teil Zählerstand .def OCR2L =r14 // ungepuffert .def OCR2H =r15 .def r =r4 // 0=AUS, 255=voll, logarithmisch skaliert .def g =r5 .def b =r6 .def isrt0 =r23 // ISR-Register, muss >= R16 seinDas Beispielprogramm verwendet eine Menge Register für die ISRs. Das ist wegen der Geschwindigkeit auch erforderlich! Der ATmega8 läuft mit 8 MHz. Für die Animation als solche werden nur 8-bit-Zahlen r, g und b gezählt.
.org 0 rjmp Init reti reti reti // Timer2-Überlauf-ISR (darf sich [255 Takte minus CLI-Zeit] Zeit gönnen) // Erzeugt CPU-Last, da Aufruf alle 256 Takte (32 µs @ F_CPU == 8 MHz)! in isrt1,SREG inc TCNT2H // High-Teil in Software brne t2a movw OCR2H:OCR2L,LEN2H:LEN2L // Bei Null Wert übernehmen (Doppel-Puffer) in isrt0,TCCR2 sbr isrt0,1<<WGM21|1<<WGM20 // PWM EIN: WGM=3 out TCCR2,isrt0 out OCR2,ONES // startet erst wenn TCNT2H == 1 t2a: cp OCR2H,TCNT2H brne t2b out OCR2,OCR2L // LOW-Zeit setzen t2b: mov isrt0,OCR2H inc isrt0 // Sonderfall OCR2H == 0xFF? breq t2c cp isrt0,TCNT2H // Wenn TCNT2H == OCR2H+1 ... brne t2c in isrt0,TCCR2 cbr isrt0,1<<WGM21|1<<WGM20 // PWM AUS: WGM=0 out TCCR2,isrt0 // (COM2 bleibt 2! Löscht OC2 später) t2c: out SREG,isrt1 reti // max. 26 Takte (inkl. Aufruf + RETI)Die Interruptserviceroutine für den 8-bit-Timer2 realisiert die 16-bit-PWM als 8-bit-PWM in einer von 256 „Zählrunden“. Davor (von HIGH-Zählerstand Null ab) ist das Ausgangssignal permanent HIGH (durch Setzen von 255 in das Register OCR2), danach permanent LOW (durch Ausschalten der PWM).
WaitT1: // Wartet auf Timer1-Überlauf (bei F_CPU == 8 MHz ca. alle 8 ms) w1: sbi PORTB,0 // SLEEP-Kontrollausgabe an Pin 14 sleep // Strom sparen cbi PORTB,0 in r16,TIFR jbrc r16,2,w1 outi TIFR, 0b00000100 // EOI retDiese kleine Warteroutine wird für die Hauptschleife (Animationsprogramm) gebraucht und wartet eine volle Umrundung von 16-bit-Timer1.
Init: clr ZERO clr ONES dec ONES outihl SP,RAMEND // Stackpointer initialisieren outi TIMSK, 0b01000000 // Timer2: Interrupt aktivieren outi TCCR2, 0b01101001 // Timer2: VT1, schnelle nichtinv. PWM outi TCCR1A, 0b10100010 // Timer1: schnelle nichtinv. PWM outi TCCR1B, 0b00011001 // Timer1: VT1 outhl ICR1,ONES,ONES // Timer1: Volle 16 bit Zählumfang mov r,ZERO mov g,ZERO mov b,ONES // Start mit 100 % Blau outi DDRB, 0b00011111 // Ausgabeports outi MCUCR, 0b10000000 // SLEEP aktivieren, nur IDLE seiInitialisiert wird mit massivem Einsatz der „makros.i90“.
MainLoop: p1: rcall WaitT1 dec b rcall SetPwmB inc r rcall SetPwmR cp r,ONES brne p1 p2: rcall WaitT1 dec r rcall SetPwmR inc g rcall SetPwmG cp g,ONES brne p2 p3: rcall WaitT1 dec g rcall SetPwmG inc b rcall SetPwmB cp b,ONES brne p3 rjmp MainLoopDie Grafik erklärt die Funktion viel besser als jeder Text:-)
SetPwmR: mov ZL,r rcall getexp outhl OCR1A, r17,r16 in r16,TCCR1A tst r breq ppr1 sbr r16,1<<COM1A1 rjmp ppr2 ppr1: cbr r16,1<<COM1A1 ppr2: out TCCR1A,r16 ret SetPwmG: mov ZL,g rcall getexp outhl OCR1B, r17,r16 in r16,TCCR1A tst g breq ppg1 sbr r16,1<<COM1B1 rjmp ppg2 ppg1: cbr r16,1<<COM1B1 ppg2: out TCCR1A,r16 ret SetPwmB: mov ZL,b rcall getexp movw LEN2H:LEN2L,r17:r16 in r16,TCCR2 tst b breq ppb1 sbr r16,1<<COM21 rjmp ppb2 ppb1: cbr r16,1<<COM21 ppb2: out TCCR2,r16 retSo werden bei Änderung von r, g und b die PWM-Ausgänge nachgeführt.
getexp: // PE: ZL = Index 0..255 // PA: R17:R16 = logarithmischer OCR-Wert mit der Eigenschaft: // Y[ZL=0] = egal (PWM-Länge 0, ganz AUS) // Y[ZL=1] = 0 (PWM-Länge 1, minimal) // Y[ZL=255] = 65535 (PWM-Länge 65536, ganz EIN) // Die PWM des ATtiny/ATmega hat die innewohnende Eigenschaft der // Impulsausgabe: 1 Takt länger als der OCR-Wert clr ZH lsl ZL // WORD-Adresse generieren (0..510) rol ZH subi ZL,LOW(-exptab*2) // Anfangsadresse der Tabelle "addieren" sbci ZH,HIGH(-exptab*2) lpm r16,Z+ // Tabellenwert lesen lpm r17,Z+ ret exptab: .nolist .dw 0,0,1,2,3,4,5,7,8,9,10,12,13,15,16,18 .dw 19,21,22,24,26,28,30,32,34,36,38,40,42,44,47,49 .dw 52,54,57,60,63,65,68,72,75,78,81,85,89,92,96,100 .dw 104,108,113,117,122,126,131,136,141,146,152,158,163,169,175,182 .dw 188,195,202,209,216,224,232,240,248,257,265,275,284,293,303,314 .dw 324,335,346,358,369,382,394,407,420,434,448,463,478,493,509,526 .dw 543,560,578,596,616,635,655,676,698,720,743,766,790,815,841,867 .dw 894,923,951,981,1012,1044,1076,1110,1144,1180,1216,1254,1293,1333,1375,1417 .dw 1461,1506,1553,1601,1650,1701,1753,1807,1863,1920,1980,2040,2103,2168,2234,2303 .dw 2373,2446,2521,2598,2678,2760,2844,2931,3021,3113,3208,3306,3407,3511,3618,3729 .dw 3842,3959,4080,4204,4332,4464,4600,4740,4885,5033,5186,5344,5507,5674,5847,6024 .dw 6207,6396,6590,6790,6997,7209,7428,7654,7886,8125,8372,8626,8887,9157,9435,9721 .dw 10016,10319,10632,10954,11287,11629,11981,12344,12718,13104,13501,13910,14331,14765,15212,15673 .dw 16148,16637,17140,17659,18194,18745,19312,19897,20499,21120,21759,22417,23096,23795,24515,25257 .dw 26021,26808,27619,28455,29316,30203,31117,32058,33028,34027,35056,36116,37209,38334,39494,40688 .dw 41919,43186,44492,45838,47224,48652,50123,51639,53201,54809,56467,58174,59933,61745,63612,65535Um aus einem „augenscheinlich linearen“ Helligkeitswert die zugehörige PWM-Einschaltzeit zu ermitteln, ist eine Exponentialtabelle hilfreich. Diese wurde mit einem awk-Programm erstellt, und zwar derart, dass:
Der Linearisierungseffekt für's Auge ist von den verwendeten LEDs sowie der Umgebungshelligkeit abhängig. Jedenfalls sind hier kleine Helligkeiten so fein wie technisch möglich gequantelt.