Pfad:
Home ==>
Mikrobeginner ==> 8. Helligkeitsregelung
This page in English (external):
Lektion 8: Helligkeitsregelung mit AD-Wandler
Mit dieser Lektion wird der im Prozessor integrierte AD-Wandler in Betrieb gesetzt.
8.0 Übersicht
- Einführung in die AD-Wandlung
- Einführung in die PCINT-Programmierung
- Hardware, Bauteile, Aufbau
- Helligkeitsregelung
- Helligkeitsregelung mit Farbwechsel
- Helligkeitsregelung dynamisch
- Rot/grün
AD-Wandler machen folgendes:
- Sie speichern zu Beginn des Messzeitraums eine Spannung und entkoppeln sie dann von
der Quelle ("Sample-and-Hold").
- Dann vergleicht er diese Spannung mit der Hälfte der Spannungsreferenz. Ist die
Spannung kleiner als die Hälfte der Spannungsreferenz, ist das oberste Bit Null,
andernfalls Eins.
- Der nächste Vergleichswert ist die Häfte der Spannungsreferenz plus ein Viertel
der Spannungsreferenz (oberstes Bit = Eins) oder ein Viertel der Spannungsreferenz
(oberstes Bit = Null). Ist die Spannung höher als diese Vergleichsspannung, ist das
zweithöchste Bit Eins, andernfalls Null.
- So geht es weiter (mit einem Achtel, Sechzehntel, 32-stel, etc. der Spannungsreferenz),
bis alle Bits festgestellt sind.
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.
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).
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.
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.
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.
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.
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.
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.
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.
8.3.1 Schaltung
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
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
Das hier ist der Aufbau. Die Platzierung der Bauteile ist unkritisch.
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
- die LED vom Timer im Fast-PWM-Modus gesteuert werden muss,
- die Stellung des Poti regelmäßig gemessen werden muss,
- der Messwert, der zwischen 0 und 1023 liegen kann, auf den Vergleicher-Wertebereich
von 0 bis 255 zurechtgestutzt werden muss, und
- der Vergleicherwert mit diesem umgerechneten Wert gesetzt werden muss.
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
- den AD-Wandler starten,
- warten müssen, bis dieser fertig ist,
- den Wert teilen und in das Vergleichsregister schreiben müssen,
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.
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.
8.4.4 Simulation der Programmausführung
Die folgende Simulation verwendet wieder den Simulator
avr_sim.
Die Initiierungsphase des Programms ist durchlaufen, 18,3 µs
sind vergangen.
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.
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.
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.
Die AD-Wandlung ist gestartet, der Umwandlungsfortschritt wird angezeigt.
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.
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.
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.
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.
Der Timerstand TOP wurde erreicht und der Vergleichswert wurde
neu eingestellt.
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.
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.
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:
- eine Flagge, die nach erkanntem Tastendruck erst mal jede weitere Aktivität am
Tasteneingang abwehrt, und
- die erst nachdem die Taste wieder losgelassen wurde und nach einer angemessenen Wartezeit
zurückgesetzt wird.
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:
- der ADC-Interrupt, der mit 721 Hz Wiederholfrequenz, also alle 1,39 ms
zuschlägt,
- den TC0, der das PWM-Signal mit 73 Hz erzeugt und dessen Vergleicherwert im
Fast-Modus bei 255 mit einem Interrupt alle 13,68 ms verwendbar wäre.
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.

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:
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.
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:
- MOV Register,Register: kopiert den Inhalt des zweiten Registers in das
erste,
- EOR Register,Register: exklusiv-oder beider Register, Ergebnis in das
erste Register (kehrt für alle Bits im ersten Register den Wert um, die im zweiten
Register an der gleichen Position Eins gesetzt sind).
- INC Register: erhöht das Register um Eins.
- BRCC Label: verzeigt zum Label, wenn das Übertragbit gelöscht
ist.
- SUBI Register,Konstante: subtrahiere die Konstante vom Register
(R16..R31).
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
...
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
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.
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.
©2016-2018 by http://www.gsc-elektronic.net