Home ==> Mikrobeginner ==> 8. Helligkeitsregelung
ATtiny13

Lektion 8: Helligkeitsregelung mit AD-Wandler


Mit dieser Lektion wird der im Prozessor integrierte AD-Wandler in Betrieb gesetzt.

8.0 Übersicht

  1. Einführung in die AD-Wandlung
  2. Einführung in die PCINT-Programmierung
  3. Hardware, Bauteile, Aufbau
  4. Helligkeitsregelung
  5. Helligkeitsregelung mit Farbwechsel
  6. Helligkeitsregelung dynamisch
  7. Rot/grün

8.1 Einführung in die AD-Wandlung

AD-Wandler machen folgendes: Der im ATtiny13 eingebaute AD-Wandler hat eine Auflösung von 10 Bits, kann also ein 1.024-stel oder ca. 1 Promille der verwendeten Referenzspannung messen. Das Ergebnis einer Messung folgt der Formel Ergebnis = (Messspannung * 1024) / Spannungsreferenz. Als Spannungsreferenz kann mit dem Bit REFS0 zwischen der Betriebsspannung (REFS0 = 0) und einer internen Spannungsreferenz von 1,1 V (REFS0 = 1) gewählt werden.

ADCH_ADCL Das Ergebnis braucht den Port ADCL (niedrigere 8 Bit) und zwei Bits des Ports ADCH (die nicht benötigten 6 Bits ergeben beim Lesen immer Null).

ADLAR Mit dem Bit ADLAR lässt sich dieses Verhalten ändern: mit ADLAR = 1 werden die 10 Bits linksbündig geschoben (LAR = Left Adjust Result). Im Port ADCH lassen sich dann die obersten 8 Bit des Ergebnisses lesen. Werden nur acht Bit benötigt kann auf das Lesen und Auswerten von ADCL verzichtet werden.

ADMUX In diesem Port befinden sich die Bits REFS0 und ADLAR. Die beiden Bits MUX1 und MUX0 wählen aus, von welchem Eingang das Messsignal kommt (ADC0 bis ADC3). Sollen mehrere Eingänge gemessen werden, muss der MUX-Port nacheinander gewählt und der Wandlungsprozess angestoßen werden.

DIDR0 Der oder die Eingangspin(s), der/die als ADC-Eingang/-Eingänge verwendet wird/werden, wird/werden nicht als normaler Eingang verwendet. Um Strom zu sparen, können diese Eingangsstufen stillgelegt werden. Das geschieht, indem die entsprechenden Bits im Port DIDR0 Eins gesetzt werden. Man beachte die eigenwillige Nummerierung der Bits.

ADCSRA Die gesamte restliche Steuerung des ADC erfolgt im Port ADCSRA. Darin bedeuten ADPS2 bis 0 einen Vorteiler für das Taktsignal (000 = 2, 111 = 128), der die Dauer der Wandlungen steuert. Pro Wandlung werden 13 vorgeteilte Taktsignale benötigt. ADIE ermöglicht den Interrupt, wenn die Wandlung erfolgt ist. ADIF wird als Flagge gesetzt, wenn der Interrupt eintritt. ADATE ermöglicht den Autostart, wenn bestimmte Bedingungen eintreten (sonst muss der Start per Programm erfolgen). ADSC startet die Wandlung und bleibt solange Eins, bis die Wandlung abgeschlossen ist. ADEN schaltet den AD-Wandler ein.

Eine typische Sequenz zum Starten des ADC beim Kanal ADC3, rechtsbündig, mit der Betriebsspannung als Referenz, Interrupt und mit niedrigster Taktrate sieht dann so aus:

	ldi rmp,(1<<MUX1)|(1<<MUX0) ; Kanal 3
	out ADMUX,rmp ; in AD-Multiplexer
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; an ADC-Kontrollregister

Das waren die wichtigsten Verhältnisse, die zur Verwendung des ADC nötig sind.

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.2 Einführung in die PCINT-Programmierung

In der vorherigen Lektion wurde die Taste am INT0-Eingang für Schaltaufgaben verwendet. Ist der Eingang INT0 nicht zu verwenden und blockiert oder muss eine weitere Taste überwacht werden, muss das mit dem PCINT-Interrupt erfolgen. Der PCINT funktioniert etwas anders als der INT0.

PCMSK Durch den PCINT sind alle Eingabepins überwachbar. Soll ein Kanal auf Pegelwechsel hin überwacht werden, wird sein entsprechendes Bit in der PCINT-Maske PCMSK auf Eins gesetzt. Sind zwei oder mehr dieser Bits gesetzt, muss per Software festgestellt werden, an welchem der Pins ein Wechsel erfolgt ist. Im Gegensatz zum INT0 kann die zu überwachende Flankenart nicht voreingestellt werden, alle Wechsel lösen einen Int aus.

GIMSK Der entsprechende Interrupt muss im General Interrupt Mask Register durch Setzen von PCIE ermöglicht werden.

Das ist schon alles, was es braucht, um Überwachung jedes beliebigen Pins zu realisieren.

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.3 Hardware, Bauteile und Aufbau

8.3.1 Schaltung

Schaltbild Die Duo-Led ist wie bei der voherigen Schaltung angeschlossen. Der Taster ist an Pin 2, den PCINT3 verlegt. Das Poti ist mit dem Schleifer an ADC2 an Pin 3 angeschlossen, er teilt die Betriebsspannung auf. Die ISP-Anschlüsse sind wie bisher angeschlossen, hier aber nicht eingezeichnet.

8.3.2 Bauteile

Das Potentiometer Poti Poti Das hier ist das Poti. Der mittlere Anschluss ist der Schleifer. Alle drei Anschlüsse sind etwas zu breit für die Breadboard-Löcher, deshalb kriegen sie kurze Anschlussdrähte angelötet.

8.3.3 Der Aufbau

Aufbau Das hier ist der Aufbau. Die Platzierung der Bauteile ist unkritisch.


Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.4 Helligkeitsregelung

8.4.1 Aufgabe 1

In der ersten Aufgabe soll die Helligkeit der roten LED mit dem Poti einstellbar sein. Die Helligkeit soll steigen, wenn das Poti nach rechts gedreht wird (höhere Spannung).

8.4.2 Lösung

Es ist klar, dass dazu Damit wir das Teilen des 10-Bit-Wertes, der vom ADC kommt, durch vier lernen, gehen wir erst mal nicht über das ADLAR-Bit des ADC.

Eigentlich ist diese Aufgabe auch linear lösbar, da wir nur während der TC0-Timer im Fast-PWM-Mode das Ansteuern der LED übernimmt. Bei diesem Vorgehen wartet der Prozessor in einer Schleife 95% seiner Zeit auf die Fertigmeldung des ADC. Da das unintelligent, unästhetisch, Verschwendung von Strom und da bei den nachfolgenden Aufgaben sowieso der ADC-Interrupt vonnöten ist, machen wir das gleich im Interruptmodus.

8.4.3 Programm 1

Das Programm gibt es hier im Quellcode.

;
; *************************************
; * Helligkeitsregler mit ADC         *
; * (C)2016 by www.gsc-elektronic.net *
; *************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; --------- Programmablauf --------------
;
; Das Programm liest laufend AD-Wandler-
; werte ein, teilt sie durch vier und
; schreibt sie in das Vergleichsregister
; einer 8-Bit-PWM im Timer.
; Der Timer schaltet den Ausgang PB0
; beim Beginn des PWM-Zyklusses auf Null
; und bei Erreichen des Vergleichswerts
; auf Eins. Damit steigt die Helligkeit
; mit der Potentiometerstellung linear an.
;
; ---------- Register ---------------------
; frei: R0 .. R12
.def rAdcL = R13 ; LSB ADC-Ergebnis
.def rAdcH = R14 ; MSB dto.
.def rSreg = R15 ; Sicherungsregister
.def rmp   = R16 ; Vielzweckregister
.def rimp  = R17 ; Vielzweckregister Interrupts
;
; ---------- Ports ------------------------
.equ pOut = PORTB ; Ausgabeport
.equ pDir = DDRB  ; Richtungsport
.equ bRAO = PORTB2 ; Ausgabe Anode rote LED
.equ bRAD = DDB2   ; Richtung Anode rote LED
.equ bRKO = PORTB0 ; Ausgabe Kathode rote LED
.equ bRKD = DDB0   ; Richtung Kathode rote LED
;
; ---------- Timing -----------------------
; Prozessortakt = 1.200.000 Hz
; ADC-Vorteiler =       128
; ADC-Zyklen    =        13
; Messfrequenz  =       721 Hz
; TC0-Vorteiler =        64
; PWM-Stufen    =       256
; PWM-Frequenz  =        73 Hz
;
; ---------- Rest- und Interruptvektoren ---
.CSEG ; Assemblieren in den Flashspeicher (Code Segment)
.ORG 0 ; Adresse auf Null (Reset- und Interruptvektoren beginnen bei Null)
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	reti ; PCINT-Int, nicht aktiv
	reti ; TIM0_OVF, nicht aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	rjmp AdcIsr ; ADC-Int, aktiv
;
; Interrupt-Service-Routinen
;
; ADC-Ready-Interrupt
; Wird vom ADC immer dann ausgeloest, wenn
; eine Wandlung erfolgt ist.
; Liest den Wandlerwert ein, teilt ihn
; durch vier und schreibt ihn in das
; Timer-Vergleichsregister A. Danach wird
; die naechste Wandlung angestossen.
;
AdcIsr: ; Interrupt Service Routine ADC
	in rSreg,SREG ; Sichern Statusregister
	; ADC-Ergebnis lesen
	in rAdcL,ADCL ; Lese ADC-Ergebnis, LSB
	in rAdcH,ADCH ; dto., MSB
	; ADC-Ergebnis durch 4 teilen
	lsr rAdcH ; unterstes Bit MSB in Carry
	ror rAdcL ; Carry in LSB schieben
	lsr rAdcH ; zweites Bit MSB in Carry
	ror rAdcL ; Carry in LSB schieben
	; neuen PWM-Wert setzen
	out OCR0A,rAdcL ; in Compare Register A
	; Neustart ADC (Teiler 128, Interrupt)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rimp ; in ADC-Kontrollregister
	out SREG,rSreg ; Wiederherstellen Status
	reti
;
; ---------- Hauptprogramm-Init ------------
Start:
	; Stapel anlegen
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf RAMEND
	out SPL,rmp ; in Stapelport
	; LEDs konfigurieren und einschalten
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Portpins auf Ausgabe
	out pDir,rmp ; in Richtungsregister
	ldi rmp,(1<<bRAO)|(1<<bRKO) ; Ausgabe auf 1
	out pOut,rmp ; in Portregister
	; Timer als 8-Bit-PWM
	ldi rmp,0x80 ; halbe Helligkeit
	out OCR0A,rmp ; in Compare Register A
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; in Kontrollregister A
	ldi rmp,(1<<CS01)|(1<<CS00) ; Vorteiler 64
	out TCCR0B,rmp ; in Kontrollregister B
	; ADC-Wandler konfigurieren und starten
	ldi rmp,1<<MUX1 ; ADC-Signaleingang = ADC2
	out ADMUX,rmp ; in ADC-Multiplexer-Port
	ldi rmp,1<<ADC2D ; Disable Porttreiber-Hardware
	out DIDR0,rmp ; in Disable-Port
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; in ADC-Kontrollregister
	; Schlafmodus und Interrupts
	ldi rmp,1<<SE ; Schlafen ermoeglichen
	out MCUCR,rmp ; in Kontrollregister
	sei ; Interrupts zulassen
; Programmschleife
Schlafen:
	sleep ; schlafen legen
	nop ; Nach Aufwecken durch Int
	rjmp Schlafen ; wieder schlafen legen
;
; Ende Quellcode
;

Neu ist hier LSR und ROR. Das müssen wir näher ansehen, es wird bei Manipulationen von 16-Bit-Zahlen öfter gebraucht.

LEFT Aufgabe dabei ist es, die beiden niedrigsten Bits aus dem ADCH (MSB) von links her in das Register mit ADCL zu schieben. Das doppelte Rechtsschieben entspricht dem Teilen des Wertes in ADCH:ADCL durch vier, die beiden überschüssigen Bits aus ADCL fallen dabei rechts heraus. Da der ATtiny13-Prozessor kein 16-Bit-Schieben beherrscht, muss es selbst gestrickt werden. Dazu wird zunächst eine Null von links her in das MSB hineingeschoben, wobei gleichzeitig das rechts herausgeschobene Bit 0 in das Carry-Bit C des Statusregisters geschoben wird. Mit der Instruktion ROR (ROtate Right) wird der Inhalt des Carry-Bits in das LSB-Register von Links hereingeschoben. Macht man die Schritte 1 und 2 zweimal nacheinander, dann sind die beiden untersten Bits im durch vier geteilten unteren LSB angekommen.

Wieder sehen wir das gleiche Prinzip: jede Instruktion in Assembler löst genau einen Schritt des Prozessors aus. Die gesamte Operation hier braucht vier Schritte und vier einzelne Instruktionen.

Das Ergebnis ist eine feinfühlige Helligkeitsregelung der roten LED mit dem Potentiometer.

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.4.4 Simulation der Programmausführung

Die folgende Simulation verwendet wieder den Simulator avr_sim.

Simulation Init Die Initiierungsphase des Programms ist durchlaufen, 18,3 µs sind vergangen.

Port Init Der Ausgangsport wurde zum Betrieb der Duo-LED an PB2 und PB0 initiiert: die beiden Richtungsbits sind gesetzt, beide PORT-Bits sind Null und die LED ist ausgeschaltet.

TC0 init Der Timer TC0 ist in den Fast-PWM-Modus gebracht, mit Überlauf beim TOP-Wert 255 und Nullsetzen des I/O-Ports PB0 bei Compare Match A, das auf 128 (ungefähr halbe Intensität) eingestellt ist.

ADC2 Init Der AD-Wandler-Kanal ADC2 ist ausgewählt. Er arbeitet mit der Betriebsspannung von 4,8 V als Referenzspannung, die Spannung an diesem Eingang wurde zur Simulation auf 1,25 V eingestellt. Die Wandlung wird vom Prozessortakt, geteilt durch 128, getaktet. Das gelb markierte Feld in der Interruptabteilung zeigt, dass der AD-Wandler am Ende des Umwandlungsprozesses den AD-Complete-Interrupt auslösen und zu dem betreffenden Interrupt-Vektor springen wird.

AD-Wandlung läuft Die AD-Wandlung ist gestartet, der Umwandlungsfortschritt wird angezeigt.

AD-Wandlung Complete Interrupt Nach kompletter Umwandlung hat der AD-Wandler die Interruptflagge gesetzt. Die Programmausführung wird, falls kein anderer Interrupt gerade ausgeführt wird und falls kein anderer, höherwertigerer Interrupt ansteht, mit der nächsten Instruktion zum AD-Complete-Vektor verzweigen.

Zeit bis zum ersten AD-Interrupt Bis zu diesem Interrupt sind seit dem Abschluss der Initphase 1,38 ms vergangen, in denen der Prozessor geschlafen hat. Da der ADC mit 1.200.000 / 128 = 9.375 Hz getaktet wird, also mit 0,107 ms pro Takt, und da die AD-Wandlung 13 Taktschritte erfordert sind die 1,38 ms korrekt.

OCR0A aus ADC-Ergebnis Das ADC-Ergebnis
1023 * 1.25 / 5.0 = 255
wird durch vier geteilt (= 63) und in das Compare-A-Portregister OCR0A des Timers geschrieben, was aber erst beim nächstfolgenden PWM-Zyklus die Pulsweite der Duo-LED und damit deren Helligkeit bestimmt.

Update des Compare Match

Das verzögerte Beschreiben von OCR0A stellt sicher, dass über jeden PWM-Zyklus hinweg der gleiche Vergleichswert verwendet wird und nicht mittendrin ein Wechsel des Wertes erfolgt, wann auch immer der AD-Wandler fertig ist.

Vergleichswert vor TOP Vergleichswert nach TOP

Der Timerstand TOP wurde erreicht und der Vergleichswert wurde neu eingestellt.

Port nach dem Compare Match Hier wurde der ursprüngliche erste Compare-Match-A-Wert von 128 erstmals überschritten. Der I/O-Pin PB0 wurde dadurch gesetzt, die LED wurde auf grün geschaltet, was sie auch bis zum Erreichen des TOP-Wertes bleibt.

Zeit bis zum ersten Compare Match A 6,89 ms sind seit der Initialisierung vergangen, wenn der erste Compare-Match-A erreicht ist. Das entspricht
64 * (128 + 1) / 1.200.000 s
und ist korrekt. Der gesamte PWM-Zyklus dauert
64 * 256 / 1.200.000 = 13,65 ms
oder einer PWM-Frequenz von 73,24 Hz. Das ist schnell genug dass es vom menschlichen Auge nicht als Flackern wahrgenommen werden kann.

Das Registerpaar R14:R13 enthält übrigens das AD-Wandler-Resultat geteilt durch vier.

Mit Hilfe der Simulation kann Quellcode genau getestet werden. Fehler im Quellcode werden gefunden und können korrigiert werden. Auch das gesamte Timing der Abläufe kann kontrolliert und, falls nötig, auch korrigiert werden.

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.5 Helligkeitsregelung mit Farbwechsel

8.5.1 Aufgabe 2

Jetzt soll nicht nur die rote, sondern auch die Helligkeit der grünen Lampe einstellbar sein. Die Umschaltung der Lampenfarbe soll mit dem Taster erfolgen. In beiden Fällen soll das Rechtsdrehen des Potis zu größerer Helligkeit führen.

8.5.2 Entprellen

Das Umschalten der LED-Farbe von Rot auf Grün und zurück, ist ein Leichtes: wenn das PORTB2-Bit oder PINB2 auf Eins steht, wird in PORTB2 Null geschrieben, und umgekehrt.

Wie wir das Drücken der Taste feststellen können, haben wir schon in der Einführung zum PCINT gesehen. Bei dieser Aufgabe wäre aber das Prellen des Tasters fatal. Je nach Anzahl an Schwarmimpulsen kommt beim Umschalten, immer wenn beim PCINT der Taster-Eingang auf Null steht, mal Rot, mal Grün raus. Das wäre unsauber, deshalb brauchen wir einen Mechanimus, der nach einem Tastendruck nachfolgende Impulse des Tasteneinganges unterdrückt.

Dazu brauchen wir Folgendes: Als Zeitmesser könnten wir wieder jede Menge Schleifenzeugs einbauen, aber das wäre unter dem bislang erreichten Programmierniveau. Für die Zeitmessung stehen uns schon zwei Quellen zur Verfügung: Da wir den ADC-Interrupt mit dieser zusätzlichen Aufgabe nicht behelligen wollen, nehmen wir dafür den TC0-CompareA-Interrupt. Da 13 ms für die Prellerkennung etwas zu kurz wäre, verwenden wir noch einen Abwärtszähler, um wenigstens 40 ms Ruhe am Tasteneingang festzustellen.

Damit kriegen wir die nachfolgenden Zeitbeziehungen zwischen Tastensignal, Inaktivitätsflagge bTA, Farbumschaltung und dem TC0-Interrupt.

Signale

Ist die Taste gedrückt (Tasteneingang geht auf Null), während die Flagge Null ist, wechselt die LED ihre Farbe und die Flagge wird gesetzt. Der Interruptzähler wird auf seinen Anfangswert 4 gesetzt.

Bei jedem nachfolgenden Null-Impuls am Eingangspin wird der Interruptzähler wieder auf vier gesetzt. das stellt sicher, dass die Flagge erst dann wieder gelöscht wird, wenn mindestens 40 ms oder vier Compare-Interrupts nach dem letzten Tasten-Null-Signal vergangen sind.

Bei jedem PWM-Interrupt wird geprüft, ob die bTA-Flagge gesetzt ist. Wenn nicht, gibt es nichts weiter zu tun. Wenn sie Eins ist, entscheidet der Zustand am Tasteneingang über den weiteren Ablauf. Ist dieser inaktiv (=Eins), dann wird der Zähler abwärts gezählt. Ist der Zähler daraufhin Null, wird die bTA-Flagge zurückgesetzt, der Wartezeitraum ist damit abgelaufen. Ist der Tastereingang aktiv (=Null), dann wird der Wartezeitraum wieder auf vier gesetzt.

Damit sind die Ablaufdiagramme für die beiden Interrupts klar:

PCINT TC0INT Das sind sie. Durch die Zusammenarbeit beider ist sichergestellt, dass mit der Taste kein Kuddelmuddel passiert.

8.5.3 Programm 2

Jetzt nutzen wir das ADLAR-Bit des ADC, um die Schieberei und Rotiererei des 10-Bit-Wertes aus dem ADC loszuwerden. Außerdem nutzen wir bei der Umschaltung der Farben die Umpolarisierung des OC0A-Ausgangs, um rot und grün korrekt anzusteuern.

Das hier ist das Programm. Den Quellcode gibt es hier.

;
; *************************************************
; * Helligkeitsregler rot/gruen mit ADC und Taste *
; * (C)2016 by www.gsc-elektronic.net             *
; *************************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Programmablauf --------------
;
; Liest laufend die Spannung am Potentiometer-
; eingang mit dem ADC ein und schreibt die
; obersten 8 Bit des Ergebnisses in das
; Vergleichsregister des Timers.
; Der Timer steuert die Helligkeit der LED
; im 8-Bit-PWM-Modus.
; Wird die Taste gedrueckt, wechselt die Duo-
; LED ihre Farbe von rot nach gruen und zurueck.
; Durch entsprechende Massnahmen wird sicher-
; gestellt, dass nachfolgende Tastensignale
; durch Prellen keine erneute Farbumkehr
; verursachen koennen.
;
; ---------- Register ---------------------
; frei: R0 .. R14
.def rSreg = R15 ; Sicherungsregister
.def rmp   = R16 ; Vielzweckregister
.def rimp  = R17 ; Vielzweckregister Interrupts
.def rFlag = R18 ; Flaggenregister
	.equ bTA = 0 ; Taste-Aktiv-Flagge
.def rCnt  = R19 ; Zaehler fuer Prellunterdrueckung
; frei: R20 .. R31
;
; ---------- Ports ------------------------
.equ pOut = PORTB ;  Ausgabeport
.equ pDir = DDRB  ;  Richtungsport
.equ pIn  = PINB  ;  Leseport
.equ bRAO = PORTB2 ; Ausgabe Anode rote LED
.equ bRAD = DDB2   ; Richtung Anode rote LED
.equ bRAI = PINB2  ; Lesen Anode rote LED
.equ bRKO = PORTB0 ; Ausgabe Kathode rote LED
.equ bRKD = DDB0   ; Richtung Kathode rote LED
.equ bTaO = PORTB3 ; Pullup-Bit Taste
.equ bTaI = PINB3  ; Inputport Taste
.equ bTaE = PCINT3 ; PCINT-Maskenbit
.equ bAdD = ADC2D  ; Input-Disable ADC-Pin
;
; ---------- Timing -----------------------
; Prozessortakt = 1.200.000 Hz
; ADC-Vorteiler =       128
; ADC-Zyklen    =        13
; Messfrequenz  =       721 Hz
; TC0-Vorteiler =        64
; PWM-Stufen    =       256
; PWM-Frequenz  =        73 Hz
; TC0-Int-Zeit  =        13,65 ms
;
; ----------- Konstanten ------------------
.equ cTakt  = 1200000 ; Prozessortakt
.equ cPresc = 64 ; TC0-Vorteiler
.equ cPwm   = 256 ; PWM-Taktdauer
.equ cPrell = 50 ; ms Prellzeit Taster
.equ cFPwm  = cTakt/cPresc/cPwm ; Frequenz PWM in Hz
; Berechnung mit Rundung
.equ cTPwm  = (1000+cFPwm/2)/ cFPwm ; Taktdauer PWM in ms
.equ cCnt   = (cPrell+cTPwm/2) / cTPwm ; Zaehlimpulse Prellen
;
; ---------- Rest- und Interruptvektoren ---
.CSEG ; Assemblieren in den Flashspeicher (Code Segment)
.ORG 0 ; Adresse auf Null (Reset- und Interruptvektoren beginnen bei Null)
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	rjmp PcIntIsr ; PCINT-Int, aktiv
	rjmp TC0OvfIsr ; TIM0_OVF, aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	rjmp AdcIsr ; ADC-Int, aktiv
;
; Interrupt-Service-Routinen, mit Anzahl Takte
;
; PCINT wird von der sich schliessenden Taste
; ausgeloest.
; Durch Abfrage der Flagge bTA wird sicher-
; gestellt, dass die Ausloesung nur erfolgt,
; wenn die Karenzzeit vorausgehender Tasten-
; ereignisse abgelaufen ist. Tastensignale
; innerhalb der Karenzzeit verlaengern die
; Karenzzeit.
; Ist die Karenzzeit abgelaufen, wird die
; Farbe der LED umgekehrt indem die Polari-
; taeten der PWM-Ausgaenge OC0A und OC0B
; umgekehrt werden. 
;
PcIntIsr: ; Interrupt-Service-Routine PCINT
	in rSreg,SREG ; Sichern Statusregister, +1 = 1
	sbrc rFlag,bTA ; Uerspringe wenn Blockade inaktiv, +1/2 = 2/3
	rjmp PcIntIsrSet ; Setze Zaehler neu, +2 = 4
	sbic pIn,bTaI ; Ueberspringe wenn Taste = 0 ; +1/2 = 4/5
	rjmp PcIntIsrSet ; Setze Zaehler neu ; +2 = 6
	sbr rFlag,1<<bTA ; Flagge setzen, +1 = 6
	sbic pIn,bRAI ; Ueberspringe wenn Anode Rot = 0, +1/2 = 7/8
	rjmp PcIntIsrGruen ; Kathode Gruen = 1, +2 = 9
	sbi pOut,bRAO ; Setze LED Gruen, +2 = 10
	ldi rimp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00) ; Fast/clr on match, +1 = 11 
	out TCCR0A,rimp ; an Timer-Kontrollport A, +1 = 12
	rjmp PcIntIsrSet ; Setze Zaehler neu, +2 = 14
PcIntIsrGruen:
	cbi pOut,bRAO ; Setze LED Rot, + 2 = 11
	ldi rimp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00) ; Fast/set on match, +1=12
	out TCCR0A,rimp ; an Timer-Kontrollport A, +1 = 13
PcIntIsrSet:
	ldi rCnt,cCnt ; starte Zaehler neu, + 1 = 5/7/15/14
	out SREG,rSreg ; Wiederherstellen SREG, +1 = 6/8/16/15
	reti ; Rueckkehr vom Interrupt, + 4 = 10/12/20/19
;
; TC0 Overflow
; Wird vom Timer TC0 am Ende des PWM-Zyklusses
; ausgeloest.
; Vermindert bei gesetzter bTA-Flagge den Zaehler
; fuer die Karenzzeit und setzt bei Erreichen von
; Null die bTA-Flagge zurueck. Ist der Tastenein-
; gang Low, wird die Karenzzeit auf vier gesetzt.
; 
TC0OvfIsr: ; Interrupt Service Routine TC0-Overflow
	in rSreg,SREG ; Sichern Statusregister, +1 = 1
	sbrs rFlag,bTa ; Ueberspringe wenn bTA-Flagge 1, +1/2 = 2/3
	rjmp TC0OvfIsrRet ; Beenden, +2 = 4
	sbis pIn,bTaI ; Ueberspringe wenn Taste = 1, +1/2 = 4/5
	rjmp TC0OvfIsrNeu ; Neustart Zaehler, +2 = 6
	dec rCnt ; vermindere Zaehler, + 1 = 6
	brne TC0OvfIsrRet ; noch nicht Null, zurueckkehren, +1/2 = 7/8
	cbr rFlag,1<<bTa ; bTa-Flagge loeschen, +1 = 8
	rjmp TC0OvfIsrRet ; Zurueck, +2 = 10
TC0OvfIsrNeu:
	ldi rCnt,cCnt ; Neustart Abwaertszaehler, +1 = 7
TC0OvfIsrRet:
	out SREG,rSreg ; Wiederherstellen Statusregister, +1 = 5/9/8
	reti ; Rueckkehr vom Interrupt, + 4 = 9/13/12
;
; ADC-Ready Int
; Liest die obersten 8 Bit des ADC-Werts ein,
; schreibt ihn in das Vergleichsregister A
; des Timers und startet die AD-Wandlung neu.
;
AdcIsr: ; Interrupt Service Routine ADC
	; ADC-Ergebnis lesen
	in rimp,ADCH ; Lese ADC-Ergebnis MSB, +1 = 1
	; neuen PWM-Wert setzen
	out OCR0A,rimp ; in Compare Register A, +1 = 2
	; Neustart ADC (Teiler 128, Interrupt)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); +1=3
	out ADCSRA,rimp ; in ADC-Kontrollregister, +1 = 4
	reti ; +4 = 8
;
; ---------- Hauptprogramm-Init ------------
Start:
	; Stapel anlegen
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf RAMEND
	out SPL,rmp ; in Stapelport
	; LEDs konfigurieren und einschalten
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Portpins auf Ausgabe
	out pDir,rmp ; in Richtungsregister
	ldi rmp,(1<<bRKO)|(1<<bTaO) ; LED auf Rot, Tasten-Pullup
	out pOut,rmp ; in Port-Outputregister
	; Timer als 8-Bit-PWM
	ldi rmp,0x80 ; halbe Helligkeit
	out OCR0A,rmp ; in Compare Register A
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; in Kontrollregister A
	ldi rmp,(1<<CS01)|(1<<CS00) ; Vorteiler 64
	out TCCR0B,rmp ; in Kontrollregister B
	ldi rmp,1<<TOIE0 ; Overflow Interrupt
	out TIMSK0,rmp ; in Timer-Int-Maske
	; ADC konfigurieren: Left adjust, Signaleingang = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; in Register
	out ADMUX,rmp ; in ADC-Multiplexer-Port
	; ADC-Eingangstreiber abschalten
	ldi rmp,1<<bAdD ; Disable Porttreiber
	out DIDR0,rmp ; in Disable-Port
	; ADC einschalten und starten
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; in ADC-Kontrollregister
	; PCINT-Enable
	ldi rmp,1<<bTaE ; Tasten-Interrupts
	out PCMSK,rmp ; in PCINT-Maskenregister
	ldi rmp,1<<PCIE ; PCINT einschalten
	out GIMSK,rmp ; in Extern Interrupt Register
	; Schlafmodus und Interrupts
	ldi rmp,1<<SE ; Schlafen ermoeglichen
	out MCUCR,rmp ; in Kontrollregister
	sei ; Interrupts zulassen
; Programmschleife
Schlafen:
	sleep ; schlafen legen
	nop ; Nach Aufwecken durch Int
	rjmp Schlafen ; wieder schlafen legen
;
; Ende Quellcode
;

Zunächst ergibt die genaue Zählung der Anzahl Takte in den Interrupt-Service-Routinen maximal 20 Takte oder 20/1,2 = 16,7 µs Dauer. Das bleibt deutlich unter den Millisekunden, die typisch für das Prellen sind. Es bleibt auch weit unter der Dauer für einen PWM-Zyklus des Timers. Die Routinen sind auch kurz genug, so dass eine Verlegung von Teilen in das Hauptprogramm unnötig ist.

Neue Instruktionen kommen darin nicht vor.

Wenn wir uns bei diesen Abläufen vorstellen, dies alles mittels genau konstruierter Schleifen zu erledigen, wird uns in jedem Fall schummrig vor Augen. Deshalb sind Interrupts so einfach wie hilfreich.

Noch ein Wort zur Programmierung der Aufgabe in Hochsprachen: Die ist ziemlich unmöglich, weil wir schon beim Timing in immense Schwierigkeiten laufen. Das Verweben zweier Interrupt-Service-Routinen miteinander dürfte oberste Programmierkunst nötig machen. Hochsprachen sind eben in Wirklichkeit nicht einfacher und für komplexe Aufgaben geeigneter als Assembler. Sie sind einfach nur ganz weit weg von der verfügbaren Hardware.
Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.6 Helligkeitsregelung dynamisch

8.6.1 Aufgabe 3

Bei dieser Aufgabe steigt bzw. fällt die Helligkeit der LED automatisch. Die Geschwindigkeit, mit der das geschieht, wird durch das Poti bestimmt. Der Taster schaltet zwischen roter und grüner LED um. Die Umschaltung von steigender auf fallende Helligkeit und umgekehrt erfolgt bei maximaler bzw. minimaler Helligkeit.

8.6.2 Lösung

Zusätzlich muss im TCO-Interrupt, der nach Ablauf eines PWM-Zyklusses eintritt, nicht nur das Timing des Tastendrucks ausgewertet werden. Es muss auch ausgewertet werden, ob der Vergleichswert erhöht/vermindert werden muss. Damit die Geschwindigkeit geregelt werden kann, muss hier ein Abwärtszähler hinzugebastelt werden. Sein Anfangswert bestimmt darüber, nach wieviel PWM-Zyklen diese Erhöhung/Erniedrigung erfolgt. Dieser Anfangswert wird vom AD-Wandler gesetzt, indem das 8-Bit-MSB des ADC (mit gesetztem ADLAR-Bit) durch 16 geteilt wird. Damit bei einem Wert von Null keine ewige Verlängerung eintritt (ein Abwärtszähler mit Null zu Beginn würde erst nach 256 Durchläufen wieder Null werden), wird zu dem Wert einfach eine Eins dazugezählt. Der Anfangswert kann daher zwischen 1 und 16 liegen.

Aus Erfahrung dauert so ein Durchlauf trotzdem noch ewig lang, weil er 256 einzelne Stufen durchläuft. Wir erhöhen daher den Zählertakt um das Achtfache (Vorteiler = 8 statt 64). Das ergibt vernünftige und merkliche Dynamik bei der LED.

Ansteigende und fallende Helligkeit lässt sich durch Verändern des OCR0A-Wertes realisieren. Die Feststellung, ob bei ansteigender Helligkeit der OCR0A-Wert von 255 auf Null und bei fallender Helligkeit der OCR0A-Wert von Null auf 255 wechseln würde und dann eine Richtungsumkehr vorgenommen werden muss, sind etwas längerwierige Entscheidungen. Dieser Teil wird daher in die Hauptprogrammschleife verlegt, Bearbeitungsbedarf wird mit einer Flagge signalisiert.

In der Hauptprogrammschleife wird auch die Farbe der LED eingestellt. Diese steht einfach als ein Bit im Flaggenregister. Der Tastendruck kehrt dieses Bit um, der Farbwechsel wird aber erst nach Ablauf der vorgewählten Anzahl PWM-Durchläufe und durch die Bearbeitung im Hauptprogramm an die LED weitergegeben.

Damit sind alle Elemente des Programmes geklärt und es kann ans Kodieren gehen.

8.6.3 Programm 3

Das hier ist das Programm. Den Quellcode gibt es hier.

;
; *************************************************
; * Helligkeitsregler rot/gruen mit ADC und Taste *
; * (C)2016 by www.gsc-elektronic.net             *
; *************************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Programmablauf ------------
; 
; Eine Duo-LED wird mit dem Taster von
; rot auf gruen umgestellt und zurueck
; (mit Prellunterdrueckung). Die Hellig-
; keit nimmt dabei zu und ab. Die Ge-
; schwindigkeit der Zu- und Abnahme wird
; von der Potentiometerstellung bestimmt.
;
; ---------- Register ---------------------
; frei: R0 .. R12
.def rCyc  = R13 ; Zykluszaehler
.def rAdc  = R14 ; ADC-Ergebnis
.def rSreg = R15 ; Sicherungsregister
.def rmp   = R16 ; Vielzweckregister
.def rimp  = R17 ; Vielzweckregister Interrupts
.def rFlag = R18 ; Flaggenregister
	.equ bTA = 0 ; Taste-Aktiv-Flagge
	.equ bCy = 1 ; Behandlungsflagge Zyklusende
	.equ bAb = 2 ; Abwaertszaehlen
	.equ bGn = 3 ; LED gruen gewaehlt
.def rCnt  = R19 ; Zaehler fuer Prellunterdrueckung
; frei: R20 .. R31
;
; ---------- Ports ------------------------
.equ pOut = PORTB ;  Ausgabeport
.equ pDir = DDRB  ;  Richtungsport
.equ pIn  = PINB  ;  Leseport
.equ bRAO = PORTB2 ; Ausgabe Anode rote LED
.equ bRAD = DDB2   ; Richtung Anode rote LED
.equ bRAI = PINB2  ; Lesen Anode rote LED
.equ bRKO = PORTB0 ; Ausgabe Kathode rote LED
.equ bRKD = DDB0   ; Richtung Kathode rote LED
.equ bTaO = PORTB3 ; Pullup-Bit Taste
.equ bTaI = PINB3  ; Inputport Taste
.equ bTaE = PCINT3 ; PCINT-Maskenbit
.equ bAdD = ADC2D  ; Input-Disable ADC-Pin
;
; ---------- Timing -----------------------
; Prozessortakt = 1.200.000 Hz
; ADC-Vorteiler =       128
; ADC-Zyklen    =        13
; Messfrequenz  =       721 Hz
; TC0-Vorteiler =         8
; PWM-Stufen    =       256
; PWM-Frequenz  =       585 Hz
; TC0-Int-Zeit  =         1,7 ms
; Zaehlzeit min =         1,7 ms
; Zaehlzeit max =        28,9 ms
; Auf-/Ab-Zyklus (min)=   0,88 s
;                (max)=  14,8 sec
;
; ----------- Konstanten ------------------
.equ cTakt  = 1200000 ; Prozessortakt
.equ cPresc = 8 ; TC0-Vorteiler
.equ cPwm   = 256 ; PWM-Taktdauer
.equ cPrell = 50 ; ms Prellzeit Taster
.equ cFPwm  = cTakt/cPresc/cPwm ; Frequenz PWM in Hz
; Berechnung mit Runden
.equ cTPwm  = (1000+cFPwm/2)/ cFPwm ; Taktdauer PWM in ms
.equ cCnt   = (cPrell+cTPwm/2) / cTPwm ; Zaehlimpulse Prellen
;
; ---------- Rest- und Interruptvektoren ---
.CSEG ; Assemblieren in den Flashspeicher (Code Segment)
.ORG 0 ; Adresse auf Null (Reset- und Interruptvektoren beginnen bei Null)
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	rjmp PcIntIsr ; PCINT-Int, aktiv
	rjmp TC0OvfIsr ; TIM0_OVF, aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	rjmp AdcIsr ; ADC-Int, aktiv
;
; Interrupt-Service-Routinen, mit Anzahl Takte
;
; PCINT Interrupt
; Wird vom Tastendruck ausgeloest.
; Ist die bTA-Flagge gesetzt, wird die Karenz-
; zeit neu gestartet. Ist der Tasteneingang
; Eins, ebenfalls. Wenn beides nicht, wird die
; bTA-Flagge gesetzt und die Farbflagge inver-
; tiert.
;
PcIntIsr: ; Interrupt-Service-Routine PCINT
	in rSreg,SREG ; Sichern Statusregister, +1 = 1
	sbrc rFlag,bTA ; Uerspringe wenn Blockade inaktiv, +1/2 = 2/3
	rjmp PcIntIsrSet ; Setze Zaehler neu, +2 = 4
	sbic pIn,bTaI ; Ueberspringe wenn Taste = 0 ; +1/2 = 4/5
	rjmp PcIntIsrSet ; Setze Zaehler neu ; +2 = 6
	sbr rFlag,1<<bTA ; Flagge setzen, +1 = 6
	ldi rimp,1<<bGn ; Kehre Flagge Gruen um, +1 = 7
	; EOR kehrt alle Bits in rFlag um, die in rimp
 	; Eins sind (hier das bGn-Bit) 
	eor rFlag,rimp ; aus rot wird gruen, aus gruen rot, +1 = 8
PcIntIsrSet:
	ldi rCnt,cCnt ; starte Zaehler neu, + 1 = 5/7/9
	out SREG,rSreg ; Wiederherstellen SREG, +1 = 6/8/10
	reti ; Rueckkehr vom Interrupt, + 4 = 10/12/14
;
; TC0 Overflow Interrupt
;
; Wird vom Timer am Ende jedes PWM-Zyklusses
; ausgeloest.
; Falls die bTA-Flagge gesetzt ist, wird
; der Tasteneingang abgefragt. Ist dieser
; High, wird der Karenzzaehler vermindert
; und bei Erreichen von Null die bTA-Flagge
; geloescht.
; Ist die bTA-Flagge nicht gesetzt oder
; ist ist der Karenzzaehler noch nicht
; Null wird ein Zykluszaehler abwaerts
; gezaehlt. Ist dieser Null, wird die
; bCy-Flagge gesetzt, die nach dem
; SLEEP bearbeitet wird.
;
TC0OvfIsr: ; Interrupt Service Routine TC0-Overflow
	in rSreg,SREG ; Sichern Statusregister, +1 = 1
	sbrs rFlag,bTa ; Ueberspringe wenn bTA-Flagge 1, +1/2 = 2/3
	rjmp TC0OvfIsrDwn ; Beenden, +2 = 4
	sbis pIn,bTaI ; Ueberspringe wenn Taste = 1, +1/2 = 4/5
	rjmp TC0OvfIsrNeu ; Neustart Zaehler, +2 = 6
	dec rCnt ; vermindere Zaehler, + 1 = 6
	brne TC0OvfIsrDwn ; noch nicht Null, weiter, +1/2 = 7/8
	cbr rFlag,1<<bTa ; bTa-Flagge loeschen, +1 = 8
	rjmp TC0OvfIsrDwn ; weiter, +2 = 10
TC0OvfIsrNeu:
	ldi rCnt,cCnt ; Neustart Abwaertszaehler, +1 = 7
TC0OvfIsrDwn:
	dec rCyc ; zaehle Zyklen abwaerts, +1 = 5/9/11/8
	brne TC0OvfIsrRet ; noch nicht Null, +1/2 = 6/7/12/13/9/10
	mov rCyc,rAdc ; Neustart Downzaehler, +1 = 7/13/10
	sbr rFlag,1<<bCy ; Setze Behandlungsflagge, +1 = 8/14/11
TC0OvfIsrRet:
	out SREG,rSreg ; Wiederherstellen Statusregister, +1 = 8/13/11/9/15/12
	reti ; Rueckkehr vom Interrupt, + 4 = 12/17/15/13/19/16
;
; ADC Ready Interrupt
;
; Wird vom AD-Wandler am Ende jedes Wandlungs-
; zyklusses ausgeloest.
; Liest die oberen 8 Bits des Ergebnisses ein
; und teilt dieses durch 16 und legt es im
; Register rAdc ab. Die Wandlung wird neu
; gestartet.
;
AdcIsr: ; Interrupt Service Routine ADC
	; ADC-Ergebnis lesen
	in rAdc,ADCH ; Lese ADC-Ergebnis MSB, +1 = 1
	lsr rAdc ; teile durch 16, +1 = 2
	lsr rAdc ; +1 = 3
	lsr rAdc ; +1 = 4
	lsr rAdc ; +1 = 5
	inc rAdc ; +1 = 6
	; Neustart ADC (Teiler 128, Interrupt)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0); +1=7
	out ADCSRA,rimp ; in ADC-Kontrollregister, +1 = 8
	reti ; +4 = 12
;
; ---------- Hauptprogramm-Init ------------
Start:
	; Stapel anlegen
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf RAMEND
	out SPL,rmp ; in Stapelport
	; LEDs konfigurieren und einschalten
	ldi rmp,(1<<bRAD)|(1<<bRKD) ; Portpins auf Ausgabe
	out pDir,rmp ; in Richtungsregister
	ldi rmp,(1<<bRKO)|(1<<bTaO) ; LED auf Rot, Tasten-Pullup
	out pOut,rmp ; in Port-Outputregister
	; Startbedingungen
	clr rFlag ; Flaggen auf Null
	ldi rmp,0x10 ; kurzer Zyklus
	mov rAdc,rmp ; in ADC-Register
	mov rCyc,rmp ; in Zykluszaehler
	; Timer als 8-Bit-PWM
	ldi rmp,0x80 ; halbe Helligkeit
	out OCR0A,rmp ; in Compare Register A
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; in Kontrollregister A
	ldi rmp,(1<<CS01) ; Vorteiler 8
	out TCCR0B,rmp ; in Kontrollregister B
	ldi rmp,1<<TOIE0 ; Overflow Interrupt
	out TIMSK0,rmp ; in Timer-Int-Maske
	; ADC konfigurieren: Left adjust, Signaleingang = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; ADLAR und ADC-Pin in Register
	out ADMUX,rmp ; in ADC-Multiplexer-Port
	; ADC-Eingangstreiber abschalten
	ldi rmp,1<<bAdD ; Disable Porttreiber
	out DIDR0,rmp ; in Disable-Port
	; ADC einschalten und starten
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; in ADC-Kontrollregister
	; PCINT-Enable
	ldi rmp,1<<bTaE ; Tasten-Interrupts
	out PCMSK,rmp ; in PCINT-Maskenregister
	ldi rmp,1<<PCIE ; PCINT einschalten
	out GIMSK,rmp ; in Extern Interrupt Register
	; Schlafmodus und Interrupts
	ldi rmp,1<<SE ; Schlafen ermoeglichen
	out MCUCR,rmp ; in Kontrollregister
	sei ; Interrupts zulassen
; Programmschleife
Schlafen:
	sleep ; schlafen legen
	nop ; Nach Aufwecken durch Int
	sbrc rFlag,bCy ; Behandlungsflagge Zyklusende
	rcall Zyklus ; Behandle Flagge
	rjmp Schlafen ; wieder schlafen legen
;
; Zyklus: Angestossen vom Ablauf jedes Zyklusses
;
; Loescht die gesetzte Flagge. Wenn die Farbflagge
; gruen ist werden die PWM-Ausgaenge OC0A und OC0B
; auf gruen eingestellt. Wenn nicht auf rot.
; Ist die Abwaertsflagge nicht gesetzt, wird der
; Vergleichswert A der PWM erhoeht, wenn gesetzt
; erniedrigt. Falls dabei 0xFF bzw. 0x00 erreicht
; wird, wird das Richtungsbit umgekehrt und bei
; dem entsprechenden Anfangswert neu begonnen.
;
Zyklus: ; Flagge bearbeiten, mit Takten
	cbr rFlag,1<<bCy ; Flagge ruecksetzen, +1 = 1
	sbrc rFlag,bGn ; Gruene LED?, +1/2 = 2/3
	rjmp ZyklusGruen ; Ja schalte LED gruen, +2 = 4
	sbi pOut,bRAO ; schalte LED rot, +2 = 5
	ldi rmp,(1<<COM0A1)|(1<<WGM01)|(1<<WGM00); +1 = 6
	out TCCR0A,rmp ; +1 = 7
	rjmp ZyklusRichtung ; Richtungsumkehr, +2 = 9
ZyklusGruen:
	cbi pOut,bRAO ; schalte LED gruen, +2 = 6
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)|(1<<WGM00); +1 = 7
	out TCCR0A,rmp ; +1 = 8
ZyklusRichtung:
	in rmp,OCR0A ; PWM-Vergleichswert lesen, +1 = 10/9
	sbrc rFlag,bAb ; ueberspringe wenn Abwaerts Null, +1/2 = 11/12/10/11
	rjmp ZyklusAbwaerts ; Nach Abwaerts ; +2 = 13/12
	; Vergleichswert aufwaerts
	inc rmp ; PWM eine Stufe aufwaerts, +1 = 13/12
	brne ZyklusSet ; setzen neuen Vergleichswert, +1/2 = 14/14
	sbr rFlag,1<<bAb ; setze Abwaertsflagge, +1 = 15
	ldi rmp,0xFF ; setze auf hoechsten Wert, +1 = 16
	rjmp ZyklusSet ; setze neuen Wert, +2 = 18
ZyklusAbwaerts:
	subi rmp,1 ; eins abziehen, +1 = 14/13
	brcc ZyklusSet ; setze Wert wenn kein Ueberlauf, +1/2 = 15/16/14/15
	cbr rFlag,1<<bAb ; aufwaerts zaehlen, +1 = 16/15 
	clr rmp ; bei Null starten, +1 = 17/16
ZyklusSet:
	out OCR0A,rmp ; neuen Vergleichswert setzen, +1 = 15/15/19/17/16/18/17
	ret ; fertig, +4 = 19/19/23/21/20/22/21
;
; Ende Quellcode
;

Die folgenden Instruktionen sind neu: Im Vergleich zum vorherigen Programm läuft der Timer jetzt acht mal schneller. Dazu wurde nur die Zeile mit dem Timer-Init geändert. Bei der Berechnung der Konstanten im Definitionskopf wurde 64 durch 8 getauscht. Die Konstante, mit der die Inaktivitätszeit am Tasteneingang festgelegt wird, hat sich dadurch erhöht, denn jetzt muss mehr gezählt werden, um die 50 ms abzuwarten. Die Konstante cCnt, die jetzt zur Anwendung kommt ist cCnt=25. Das ist der Vorteil, solche Konstanten zu Beginn im Kopf zu definieren: der Änderungsbedarf im Programmcode ist sehr viel geringer.

Um herauszufinden, wie gross cCnt denn jetzt ist, hilft das Listing des Studio-Assemblers nicht weiter. Mit gavrasm kriegt man mit dem Aufrufparameter -S am Ende des Listings eine Aufstellung aller Konstanten und ihrer Dezimal- und Hexwerte. Man kann es aber auch mit dem Taschenrechner ...

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


8.7 Helligkeitsregelung rot/grün

8.7.1 Aufgabe 4

Das ist eine Bonusaufgabe. Was passiert mit der Farbe der LED, wenn wir schnell zwischen Rot und Grün hin- und herwechseln? Dazu ist eine PWM so zu schalten, dass der Regler den Anteil beider Farben an der Gesamtzeit der PWM variiert. Steigende Spannungen am Poti sollen den Rotanteil erhöhen.

8.7.2 Lösung 4

Gegentakt Zunächst kriegen wir das mit der bestehenden Hardware kaum elegant hin. Idealerweise lassen wir dazu die beiden PWMs im ATtiny13, A und B, gegenläufig laufen. Die Signalausgänge OCR0A und OCR0B liefern bei richtiger Programmierung ein gegenläufiges invertiertes Signal, das die Farben der LED umschaltet.

Schaltung Da der OC0B-Anschluss benötigt wird, muss der zweite Anschluss der Duo-LED an Pin 6 verlegt werden. Der Taster wird hier nicht gebraucht, er stört aber auch nicht.

8.7.3 Programm 4

Das hier ist das Programm. (Hier ist der Quellcode.). Da die gesamte PWM-Schalterei vollständig vom Timer übernommen wird, brauchen wir nur für den ADC-Wandler eine Interrupt-Service-Routine.

;
; *********************************
; * Duo-LED im Gegentakt ATtiny13 *
; * (C)2016 by gsc-elektronic.net *
; *********************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ------ Programmablauf ----------
;
; Liest den Potistand mit dem ADC
; ein und schaltet die Duo-LED mit
; dem TC0-PWM schnell zwischen rot
; und gruen um. Der Rot-Anteil
; steigt mit zunehmender Potispan-
; nung.
;
; -------- Register ---------------
; frei: R0 .. R15
.def rmp = R16 ; Vielzweckregister
.def rimp = R17 ; Vielzweckregister Interrupts
;
; -------- Ports ------------------
.equ pDir = DDRB ; Port-Ausgaenge
.equ bARD = DDB1 ; Rote Anode
.equ bKRD = DDB0 ; Rote Kathode
;
; -------- Timing -----------------
;  Takt         = 1200000 Hz
;  Prescaler    =      64
;  PWM-Stufen   =     256
;  PWM-Frequenz =      73 Hz
;
; -- Reset- und Interruptvektoren -
.CSEG ; Code Segment
.ORG 0 ; Bei 0 beginnen
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	reti ; PCINT-Int, nicht aktiv
	reti ; TIM0_OVF, nicht aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	rjmp AdcIsr ; ADC-Int, aktiv
;
; ----- Interrupt Service Routinen -----
;
AdcIsr:
	in rimp,ADCH ; Lese ADC-Ergebnis MSB
	out OCR0A,rimp ; in Vergleichsregister A
	out OCR0B,rimp ; in Vergleichsregister B
	; Neustart ADC (Teiler 128, Interrupt)
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rimp ; in ADC-Kontrollregister
	reti
;
; ----- Hauptprogramm Init -------------
Start:
	; Stapel einrichten
	ldi rmp,LOW(RAMEND) ; auf SRAM-Ende
	out SPL,rmp ; in Stackregister
	; Ausgaenge initiieren
	ldi rmp,(1<<bARD)|(1<<bKRD) ; Ausgaenge
	out pDir,rmp ; in Richtungsregister
	; Vergleicher initiieren
	ldi rmp,0x80 ; auf halbe/halbe
	out OCR0A,rmp ; in Vergleichsregister A
	out OCR0B,rmp ; in Vergleichsregister B
	; Timer als PWM mit A- und B-Outputsteuerung
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<COM0B1)|(1<<WGM01)|(1<<WGM00)
	out TCCR0A,rmp ; in Kontrollregister A
	; Timer mit 64 starten
	ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler 64
	out TCCR0B,rmp ; in Kontrollregister B
	; ADC konfigurieren: Left adjust, Signaleingang = ADC2
	ldi rmp,(1<<ADLAR)|(1<<MUX1) ; ADLAR und ADC-Pin in Register
	out ADMUX,rmp ; in ADC-Multiplexer-Port
	; ADC-Eingangstreiber abschalten
	ldi rmp,1<<ADC2D ; Disable Porttreiber
	out DIDR0,rmp ; in Disable-Port
	; ADC einschalten und starten
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; in ADC-Kontrollregister
	; Schlafen einschalten
	ldi rmp,1<<SE ; Sleep Enable
	out MCUCR,rmp ; in General Kontrollregister
	; Interrupts einschalten
	sei ; set I-Flagge
Schleife:
	sleep ; schlafen legen
	nop ; nach Aufwachen
	rjmp Schleife ; wieder schlafen legen
;
; Ende Quellcode
;

Hier gibt es keine neuen Instruktionen.

Home Top AD-Wandlung PCINT Helligkeit Farbwahl Dynamik Rot/grün


©2016-2018 by http://www.gsc-elektronic.net