Assembler Tiny2313 und Timer0 (evtl. für Anfänger)

Status
Für weitere Antworten geschlossen.

Rolf.H

Neues Mitglied
17. Juni 2012
136
0
0
89
25451 Quickborn
Sprachen
Hallo Leute,
da ich mich lange mit den Tiny13 und PWM aufgehalten habe, solls jetzt mit den
Tiny2313 weiter gehen.
Hierbei habe ich mein Wissen mit dem relativ einfachen Timer0 nochmal
aufgefrischt.
Ich hielt es für sinnvoll, einen neuen Thread zur eröffnen.
Vielleicht ist es was für Anfänger.
Die Aufgabenstellung ist rel. einfach. Habe es aus reiner Selbstüberlegung,
d.h. ohne Bücher geschafft.
Mit PUSH und POP stehe ich noch etwas auf dem Schlauch.
So, hier der Quellcode in Assembler:

Code:
; Projekt-Name: Projekt01                      Datum: 26.07.2012									

; Datei: Timertest01.asm              

; PORTB,PB0-PB2 = Output
; Aufgabe: zu Beginn alle 3 Pins=High
; 5 Sec.PB2=Low / 5 Sec.PB1=Low / 5 Sec.PB0=Low /
; 5 Sec. Beginn>> loop: 

; Zeit bei 1,2 MHz= 1/f =0,833ysx1024x256=0,218 Sec.
; zu 5 Sec = 5/0,218 = aufgerundet 23 dezimal = 0x17 hex.

; AVR: Tiny2313 (Systemtakt = 1,2 MHz)

           .INCLUDE   "tn2313def.inc"   ; Deklaration für Tiny2313
           
           .def    akku=r16
           .def    pause=r17
            
           rjmp    reset           ; Reseteinsprung
           .ORG    OVF0addr        ; Interrupt-Vektor
           rjmp    TIMER0_OVF      ; Sprung zur ISR


reset:     ldi     akku,0x07       ; Bitmuster 0000 0111
           out     DDRB,akku       ; Datenricht. PB0 - PB2=Output
           

; Timer0 initialisieren:
           ldi     akku,(1<<CS02)|(1<<CS00) ; Pr.= 1024
           out     TCCR0B,akku
           ldi     akku,(1<<TOIE0) ; Register TIMSK (Bit1=1)
           out     TIMSK,akku
           sei                     ; Timer frei


loop:      ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB2 = High
           rcall   zeit5

           ldi     akku,(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB1 = High
           rcall   zeit5
         
           ldi     akku,(1<<PB0)       
           out     PORTB,akku      ; PB0 = High
           rcall   zeit5

           ldi     akku,0
           out     PORTB,akku      ; PORTB =Low
           rcall   zeit5
           rjmp    loop


zeit5:     ldi     pause,0x17      ; r17<<<23
pause5:    tst     pause           ; test r17 auf Null 
           brne    pause5          ; wenn r17 keine 0 nach pause5
           ret


;Interrupt-ISR

TIMER0_OVF:push    r2        ;Kopie r2 auf den Stack, danach SP-1
           in      r2,SREG   ;Inhalt vom Statusregister in r2 laden
           dec     pause     ;Dekrement (r17-1)
           out     SREG,r2   ;Inhalt von r2 ins SREG laden
           pop     r2        ;SP+1, danach vom Stack in r2 laden
           reti
          .EXIT
Grüße

Rolf
 
nur ein paar Anmerkungen:
- in Zeile 25 lädst Du 0x07 in den Akku (um es in DDRB zu schreiben). 0x07 ist dasselbe wie 0b00000111, oder 7 dezimal, klar - an dieser Stelle würde ich es allerdings so schreiben, wie in Zeile 37, also mit "(1<<PB2)|(1<<PB1)|(1<<PB0)". Das ist zwar in diesem Fall auch dasselbe, aber erstens ist es so einfacher lesbar, und zweitens wäre es so auch einfacher auf einen anderen Controller portierbar.
- Deine Berechnungen hab ich jetzt nicht kontrolliert - die werden schon stimmen...
ABER
Dein Programm wartet fast immer darauf, daß Pause 0 wird. Du kannst also nichts weiteres einprogrammieren.
Ich würde das auf die Schnelle komplett in die Timer-ISR packen. In etwa so:
-statt pause dekrementiere ich einen Hilfszähler (nennen wir ihn countdown) bei jedem Timerüberlauf. Initialisiert wurde er vorher mit 23*4=96.
-Ist das Ergebnis=0 (also wurde dabei das Z-Flag gesetzt), wird der countdown mit 96 neu beladen und alle 3 Beinchen auf high gesetzt, ansonsten wird das ganze übersprungen (BRNE).
-überprüfe, ob countdown kleiner als 70, 47 bzw 24 ist, und lösche (CBI ?) die entsprechenden PORT-Bits der Beinchen.

Alternativ kannst Du die Reaktion auf den countdown auch ins Hauptprogramm verlagern, bis auf das zurücksetzen desselben.

Zu Push/pop schreib ich später noch was...
 
Hallo LotadaC,
danke für Deine Antwort!
Nun versuche ich, Deinen Vorschlag in die Tat umzusetzen, aber es fällt mir noch
schwer.
zum Beispiel:
ldi akku,(1<<PB2)|(1<<PB1)|(1<<PB0)
out PORTB,akku ; PB0 - PB2 = High
rcall zeit5

ldi akku,(1<<PB1)|(1<<PB0)
out PORTB,akku ; PB0 - PB1 = High
rcall zeit5
sollte ich das rcall zeit5 löschen und den Zeitablauf gesamt in die ISR packen.
Was verstehst Du unter einen "Hilfszähler"
Zu Beginn hatte ich das Arbeitsregister r17 als pause definiert.
Wäre das denn nicht ein Hilfszähler??
Soll der Hilfszähler in der ISR mit 96 initialisiert werden?
Und warum 23x4=96
Mit 1,2MHz u. Prescale=1024x256x23 komme ich an ca. 5 Secunden

Mehr Fragen will ich erst mal nicht stellen.

Grüße

Rolf
 
Tschuldigung, das mit den 96 war ein Tippfehler - 23*4=92

Ok, was macht Dein Programm eigentlich genau?
Du hast eine Endlosschleife, in dieser werden:
-als erstes die 3 Pins auf high-Pegel geschaltet
-gewartet, bis Dein Hilfszähler 0 erreicht hat (5 sec)
-ein Pin low gesetzt (und der Hilfszähler wieder zurückgesetzt)
-gewartet, bis Dein Hilfszähler (wieder) 0 erreicht hat (5 sec)
-ein weiterer Pin low
-weitere 5 sec warten
-der letzte pin low
-und nochmal 5sec warten
danach wieder von vorn.

In der ISR unterbrichst Du das laufende Programm alle 1024*256 Takte um den Hilfszähler zu dekrementieren.
Ansonsten wartet Das programm aber 5 sec auf Zähler=0 - Du könntest also genauso gut auf den Timer verzichten, und stattdessen direkt in der Warteschleife einen hinreichend breiten Zähler (3 byte) dekrementieren, mit 0 vergleichen usw.
Könntest... ich würde das wie gesagt nicht so machen.

Ok, warum Hilfszähler? Dein Zähler ist ja der Timer. Leider kann der mit 8bit-breite, und selbst mit maximalem Prescaler, deine 5 sec nicht erreichen. Deswegen mußt Du den also in Software breiter bekommen - indem Du eine weitere Variable zu hilfe nimmst. klar?

Warum 92 statt 23? Weil ich statt des Konzeptes mit dem Warten da oben sowas machen würde:
loop:
wenn Hilfszähler > 69 setze alle 3 pins high
sonst lösche pin3
wenn Hilfszähler < 46 lösche pin2
wenn Hilfsähler < 23 lösche pin1
...
irgendwelcher anderer code
...
rjmp loop

So kannst Du also weiteren code in das Programm einbauen, ohne das sich an den 3 pins nenneswert was verändert, klar?
In der Timerüberlauf-ISR würde der Hilfszähler dekrementiert, und beim Erreichen von 0 (Z-Flag) zurückgesetzt werden.
Natürlich kann das hilfszählerabhängige manipulieren der Pins auch direkt mit in die ISR - es ist halt zu entscheiden was wichtiger ist: die Reaktion der Pins auf den Timer möglichst exakt zu realisieren (was ja durch den zusätzlichen code eventuell verzögert wird), oder die ISR extrem kurz zu halten (um möglichst schnell und exakt auf andere eventuelle Interrupts reagieren zu können.

So, jetzt noch was zu Register retten. Generell müssen in einer ISR nur Register gerettet werden, die in dieser ISR verändert werden können, UND deren Inhalt ausserhalb dieser ISR (also im unterbrochenen Prozess) von irgendeiner Bedeutung sind (es sei denn, diese Änderung ist genau die Aufgabe der ISR). Folglicherweise müssen normalerweise (*) in einem Programm mit leerem Hauptprogramm gar keine Register gesichert werden. (*) Es sei denn, man erlaubt einer ISR, durch andere Interrupts unterbrochen zu werden.

Da Du aber im allgemeinen beim Erstellen der ISR nicht unbedingt weist, was später im Hauptprogramm noch so dazu kommt, empfiehlt es sich, alle(*) Register, die in der ISR manipuliert werden, zu sichern. Wird in der ISR auch das SREG manipuliert, darf auch dieses dann nicht vergessen werden (es kann aber nicht direkt gepush/gepopt werden).
(*) Ausnahme: Register, die Du für irgendwas reservierst - Pointerregister für SRAM-Zugriffe zB.
 
Hallo Rolf

Das Thema mit Push/Pop hat mich auch ewig lange irritiert. aber das ist tatsächlich eins der einfachsten Sachen bei Assembler ^^
Mit Pop legt man etwas im "Stack" ab und mit Push holt man es wider runter.

Kurzes beispiel:

Wenn du im r16 die Zahl 4 stehen hast und sie kurzfristig irgendwo ablegen willst weil du r16 grade wo anders brauchst, schreibst du einfach:
Code:
PUSH r16
jetzt wird der Wert 4 auf einem Stapel ganz oben abgelegt.
wenn du den Wert wider brauchst, schreibst du einfach folgendes:
Code:
POP r16
Jetzt ist der Wert 4 wider im r16

Du kannst auch andere Dinge mit Push/Pop anstellen. zb. könntest du den Wert in r16 in r17 kopieren.
Code:
PUSH r16 ;Wert von r16 wird auf dem Stapel abgelegt
POP r17 ; Wert wird vom stapel geholt und in r17 abgelegt

Um diese Funktion nutzen zu können musst du den Stack aber vorerst initialisiren. das geht so:

Code:
;Stack Initialisieren 
	ldi temp1, LOW(RAMEND)  
	out SPL, temp1
	ldi temp1, HIGH(RAMEND)  
	out SPH, temp1

Den Stack kannst du immer gleich am anfang von deinem Programm initialisieren. das schadet garnicht und ist bei vielen Chips notwendig um den überhaupt zum laufen zu bringen.

Gruß
 
Wie bereits in dem anderen Thread gesagt, muß der SP beim Tiny2313 nicht auf RAMEND reinitialisiert werden, da das bereits automatisch beim Reset geschieht. (Streng genommen muß bei keinem AVR irgendein I/O-Register initialisiert werden, da generell alle I/O-Register beim Reset initialisiert werden. Mit welchen Werten steht im Datenblatt - die "Initial Values" eben. ABER wenn diese Werte für die Anwendung nicht zutreffen, muß reinitialisiert werden). Beim Tiny 2313 ist bereits SPL=low(RAMEND), SPH sollte entgegen der "Memory description" gar nicht existieren, im register summary ist es auch nicht zu finden. Klar, der 2313 hat ja auch nur 128 Bytes SRAM, 64 I/O-Register (wovon nicht alle verwendet werden), und die üblichen 32 Rechenregister. Zusammen sind also nur 224 Register zu adressieren - da reicht ein Byte.
Zu "auch andere Dinge mit dem SP tun": Man kann mit dem SP (zur Laufzeit) variable Sprünge durchführen, indem man einfach die Adresse (bytes) auf den Stack pusht, und dann ein RET durchführt. (die üblichen Sprungbefehle erlauben ja nur Sprünge zu, während der Laufzeit festen Program Flash Adressen).
(Achso, die Idee stammt natürlich von Dino)
 
Hallo,

also es gehört nicht so ganz zum Thema aber ... ich finde es Klasse das mal ein wenig mehr mit Assembler gemacht wird :cool:

Es ist eigentlich die einzige Möglichkeit sich mal richtig in die Hardware reinzuarbeiten und mal auszuprobieren was möglich ist.
So .. nun genug OffTopic und weiter im Thema :D

Gruß
Dino
 
Hallo LotadaC,
ich kämpfe schon den ganzen Morgen, aber ich merke, daß mir doch
noch einige Grundkenntnisse fehlen.
Habe mir bei amazon noch ein Buch bestellt.
Speziell für Tiny2313 und Tiny 26 in Assembler.
Der Begriff "Variable" tauchte oft bei Bascom auf. In meinen Buch
kann ich nichts lesen darüber.
Ich könnte mir denken, daß es ein definiertes Arbeitsregister ist,
wie z.B .def akku=r16.
So, hier mein überarb. Quelltext, glaube nicht, daß das läuft.
Der Compieler gab keine Fehler

Code:
; Projekt-Name: Projekt01               Datum: 30.07.2012                                    

; Datei: Timertest02.asm              

; PORTB,PB0-PB2 = Output
; Aufgabe: zu Beginn alle 3 Pins=High
; 5 Sec.PB2=Low / 5 Sec.PB1=Low / 5 Sec.PB0=Low /
; 5 Sec. Beginn>> loop: 

; Zeit bei 1,2 MHz= 1/f =0,833ysx1024x256=0,218 Sec.
; zu 5 Sec = 5/0,218 = aufgerundet 23 dezimal = 0x17 hex.

; AVR: Tiny2313 (Systemtakt = 1,2 MHz)

           .INCLUDE   "tn2313def.inc"   
           
           .def    akku=r16
           .def    temp1=r17
            
           rjmp    reset           ; Reseteinsprung
           .ORG    OVF0addr        ; Interrupt-Vektor
           rjmp    TIMER0_OVF      ; Sprung zur ISR


reset:     ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0) ;dezimal=7 
           out     DDRB,akku   ; Datenricht. PB0 - PB2=Output
           

; Timer0 initialisieren:
           ldi     akku,(1<<CS02)|(1<<CS00) ; Pr.= 1024
           out     TCCR0B,akku
           ldi     akku,(1<<TOIE0) ; Register TIMSK (Bit1=1)
           out     TIMSK,akku
           sei                     ; Timer frei

loop:      ldi     temp1,0x60      ; dezim.96
           ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB2 = High
           
           ldi     temp1,0x45      ; dezim.69
           ldi     akku,(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB1 = High
           
           ldi     temp1,0x2E      ; dezim.46
           ldi     akku,(1<<PB0)       
           out     PORTB,akku      ; PB0 = High
           
           ldi     temp1,0x17      ; dezim.23
           ldi     akku,0
           out     PORTB,akku      ; PORTB =Low
          
           rjmp    loop



           

;Interrupt-ISR

TIMER0_OVF:push    r2        ;Kopie r2 auf den Stack, danach SP-1
           in      r2,SREG   ;Inhalt vom Statusregister in r2 laden
           dec     temp1      ;Dekrement (r17-1)
pause5:    tst     temp1
           brne    pause5
           out     SREG,r2   ;Inhalt von r2 ins SREG laden
           pop     r2        ;SP+1, danach vom Stack in r2 laden
           reti
          .EXIT
 
So, hier mein überarb. Quelltext, glaube nicht, daß das läuft.
Der Compieler gab keine Fehler

Ich habe nur einmal kurz draufgeschaut, da mit etwas aufgefallen ist, poste ich es.

Spätestens hier hängt das Programm, wenn temp1>0.

Code:
TIMER0_OVF:push    r2        ;Kopie r2 auf den Stack, danach SP-1
           in      r2,SREG   ;Inhalt vom Statusregister in r2 laden
           dec     temp1      ;Dekrement (r17-1)
[COLOR=#b22222]pause5:    tst     temp1
           brne    pause5[/COLOR]
           out     SREG,r2   ;Inhalt von r2 ins SREG laden
           pop     r2        ;SP+1, danach vom Stack in r2 laden
           reti
 
Hallo Dirk,
danke für Dein Info.
Meine Grundidee liegt eigentlich ganz anders.

Ich dachte:
in der ISR nur hinein "dec temp1" ;r17-1

und in der loop Schleife wird ja temp1 ein Wert vorgegeben, zB 0x96
Bei jedem Überlauf wird 96-1 gezählt.
Hat temp1<69 wird PB2 zu Low / temp1<46 PB1 zu Low / temp1<23 PB0 zu Low.
Aber dann müßte ich irgendwie einen Vergleicher ansetzen, der die 3 Werte erkennt.
Wie soll das sonst denn laufen?




Code:
; Projekt-Name: Projekt01               Datum: 30.07.2012                                    

; Datei: Timertest02.asm              


loop:      ldi     temp1,0x60      ; dezim.96
           ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB2 = High
           
           ldi     temp1,0x45      ; dezim.69
           ldi     akku,(1<<PB1)|(1<<PB0)       
           out     PORTB,akku      ; PB0 - PB1 = High
           
           ldi     temp1,0x2E      ; dezim.46
           ldi     akku,(1<<PB0)       
           out     PORTB,akku      ; PB0 = High
           
           ldi     temp1,0x17      ; dezim.23
           ldi     akku,0
           out     PORTB,akku      ; PORTB =Low
          
           rjmp    loop



           

          .EXIT
 
Hallo Rolf,

also so wie es jetzt ist, hast du in der Timer-ISR eine Endlosschleife, da tut sich dann nichts mehr ... auch dann nichts, wenn der Timerinterrupt nocheinmal auftritt.

So wie dein Hauptrogramm im Moment abläuft, brauchst du eigentlich nur ein Delay, welches sich über einen Parameter konfigurieren läßt. Das Delay "verbraucht" zwar Machinenzyklen, aber das ist durchaus sinnvoll, wenn du nicht zwischenzeitlich andere Sachen erledigen möchtest. Anstelle des Delays kannst du die Pause natürlich auch mit dem Timerinterrupt erzeugen. Man kann auch den Timer verwenden, ohne den ensprechenden Interrupt zu aktivieren und im Hauptprogramm dann das Interruptflag abfragen und manuell zurücksetzen, um ein zeitabhängiges Signal (Flag) zu erhalten.

Es kommt nun wirklich darauf an, was dein Programm zwischen den Pausen machen soll ... und ob es überhaupt etwas machen soll. Es gibt jedenfalls mehrere Möglichkeiten, wie man bestimmte Abläufe/Programmteile zeitlich steuert (Task-Management).

Du könntest zum Beispiel so vorgehen, dass du den Zähler in die TimerISR verlegst. Der Zähler wird im Hauptprogramm initialisiert und der Timer gestartet, der Interrupt ist aktiviert. Die TimerISR setzt dann nach abgelaufener Zeit ein Flag in einem Register und deaktiviert sich. Dieses Flag fragst du im Hauptprogramm ab und reagierst entsprechend drauf. (Das Flag kannst du dir auch sparen, wenn du ein Konfigurationsregister des Timers abfragst). Hier gibt es die unterschiedlichsten Möglichkeiten, welche man wählt, ist von den Anforderungen deines Hauptprogramms abhängig. Im Moment reicht einfach ein Delay, mit einer TimerISR gewinnst du nichts!

Dirk :ciao:
 
Ohje... da hab ich ja was angerichtet...

Ich meinte das so:
-Der Zähler wird in der ISR (und nur dort) dekrementiert. Wird er dabei 0 (erkennbar am Z-Flag im SREG, welches durch dec beeinflußt wird), wird wieder zurückgesetzt. Etwa so:
Code:
...
dec countdown
brne nichtnull
ldi countdown, 92
nichtnull:
...
mehr muß eigentlich nicht in die ISR - abgesehen vom Registerretten. Ok, welche werden verwendet? countdown sollte (Hilfszähler) ein reserviertes Register sein - bleibt SREG (mit dem Umweg über ein Rechenregister - wie gehabt)
In der Hauptprogrammschleife muß jetzt mittels "wenn...dann" Abfragen auf den countdown reagiert werden. Da Du also den countdown mit festen Werten vergleichen willst, verwendest Du "Compare with Immediate -CPI". CPI vergleicht ein Rechenregister mit einer Konstante, und manipuliert davon abhängig das Carry-Flag (C in SREG). Abhängig vom C-Flag kannst Du jetzt bestimmte Code-Abschnitte mittels BRSH oder BRLO überspringen, Dir also so ein If...then...else-Konstrukt zurechtbauen.

Zum manipulieren der Port-Beinchen: PortB hat die I/O-Register-Adresse 0x18. Bis 0x1F können einzelne Bits durch die Befehle Set Bit in I/O-Register (SBI) und Clear Bit in I/O-Register (CBI) einzeln und direkt gesetzt/gelöscht werden. Ohne daß dazu das entsprechende Bitmuster (Byte)aus einem Rechenregister in den I/O-Bereich kopiert werden muß (out). Also erst recht auch ohne das Bitmuster vor der Manipulation in das Rechenregister kopieren zu müssen (in).
Noch dabei? Dann leg ich jetzt noch einen (ok, zwei) drauf. Dazu passend gibt es jetzt auch noch bedingte "Sprungbefehle", die auf ein Bit irgendeines I/O-Registers (wieder nur bis 0x1F) reagieren können. Mit SBIS (Skip if Bit in I/O-Register is set) wird der nächste Befehl übersprungen, wenn das angegebene Bit im angegebenen Register gesetzt ist, mit SBIC (Skip if Bit in I/O-Register is cleared) anlog, nur eben bei gelöschtem Bit.

Zu Variablen im Allgemeinen: Wenn Du in Bascom irgendeine Variable definierst, wird (für Bascom) der entsprechende nötige Platz im SRAM reserviert. Die entsprechende Adresse im SRAM ist dann quasi der Variablenname. Manipulationen dieser Variable laufen dann über ein/die Rechenregister, meist nach dem Schema:
-laden des Variableninhaltes aus dem SRAM in ein Rechenregister
-Manipulation des Rechenregisters
-Zurückkopieren ins SRAM
Dieses Schema heißt "read-modify-write - rmw)
In ASM funkt Dir aber keine Hochsprache dazwischen - Du bist der Gott über die Register. Du kannst also auch ein beliebiges Rechenregister für irgendwas reservieren. Wenn Du ihm einen Namen gegeben hast, ist es ja eine Veriable (die vergebenen Namen existieren ja eh nur für Dich - letztendlich sind das ja alles nur Adressen im Speicher). Du kannst auch irgendwelchen SRAM-Adressen beliebige (Variablen-)Namen verpassen. Mit der Assembler-Direktive ".def". Auf diesem Weg wurde zB PORTB die Adresse 0x18 zugewiesen. Wo? in der Definitionsdatei, die Du inkludiert hast.
Welche Datentypen kennt der Assembler? Bytes.
(und in gewisser Hinsicht noch Words - die Registerpaare R25/R24, R27/R26 (=X), R29/R28 (=Y), R31/R30 (=Z) können mit bestimmten Befehlen als 16bit-Wort verwendet werden.)
Und was ist mit komplexeren Datentypen? Nun, die müssen halt aus Bytes zusammengebaut werden, ist ja bei Hochsprachen auch so. Nur, daß das da bereits "in der Sprache" zur Verfügung steht - hier mußt Du das entweder selbst machen, oder Du findest entsprechende fertige Codeteile, die Du einbinden kannst.

Das und Link das Du bereits?
(auch, wenn ich bei letzterem mit dem RETI-Zwang in der Interruptvektortabelle und einigen anderen Sachen anderer Meinung bin...)
 
Hi Rolf
Ich glaub, ich muss da nochmal ein wenig Werbung machen....:cool: Schau mal in die Rubrik FAQ und zum Beitrag "Keine Angst vor Assembler". Mir scheint, für die Interruptroutinen brauchst du noch etwas Anleitung. So zum allgemeinen Verständnis: Eine ISR ist ein selbstständiges kleines Programm was zu jeder beliebigen Zeit aufgerufen werden kann. Da du dann nicht weißt, welcher Programmteil da grad bearbeitet wird, mußt du die verwendeten Register, und das Statusregister ist eines davon, eben zwischenspeichern. Dafür gibt es Push und POP.
Der Timer ist ja eigentlich kein Timer, sondern ein Zähler. Und da mit einem Register nur 256 Impulse gezählt werden können, musst du dir da etwas einfallen lassen. Zum einen hast du Vorteiler. (Neudeutsch : Prescaler) 1024 ist nicht grad gut gewählt, da du versuchen solltest, aus der Taktfrequenz eine Ganzzahl zu erhalten: 1200000/1024=117,1875.
Nimmst du einen Teiler, der dir eine Ganzzahl liefert ist es einfacher:
1200000/8 = 150 000
Nun läßt du deinen Timer im Compare Interrupt laufen und setzt den Vergleichswert auf 150, das schafft das 1 Byte-Zählregister und schon hast du einen Interrupt, der jede msek. ausgelöst wird.
Mit "Variablen" oder fest zugeordneten Registern baust du dir dann deine Zeitbasis:
Register zuordnen:
Code:
.Def	mSek_2= R4	; Register 4 zählt bis 100msek.
.Def	zehntel= R5 	; Register 5 zählt Zehntelsekunden
.Def	Sekunde=R6 	; Register 6 zählt Sekunden
.Def 	Minuten= R7	; Register 7 zählt Minuten
.Def 	Temp = r16	; Allzweckregister
.....
Timer0_ISR:
             Push 	Temp		; Register sichern
             In 	Temp, Sreg
             Push 	Temp        	
             INC	mSek_2
             LDI	Temp, 100
             CP	mSek, Temp
             BRLO	End_Timer0_ISR
             CLR 	mSek_2
   	INC 	zehntel
             LDI	Temp, 10
             CP	zehntel, Temp
             BRLO	End_Timer0_ISR
   	Clr	zehntel
	Inc	Sekunde
	LDI	Temp, 60
	CP	Sekunde, Temp
	BRLO	End_Timer0_ISR
	Clr	Sekunde
             Inc	Minute
             CP	Minute, Temp
             BRLO	End_Timer0_ISR
             CLR	Minute
End_Timer0_ISR:
	POP	Temp
	Out	SReg, Temp
	POP	Temp
RETI
Um nun in einem Programm Zeiten zu bekommen. setze ich Zeitflags ein. Das ist im Prinzip eine Variable oder auch ein Register, welches in der ISR in einem Zeitabschnitt entsprechende Bits auf "1" setzt. In der Programmschleife prüfe ich lediglich diese Bits, wenn eins gesetzt ist, wird ein Unterprogramm aufgerufen und dieses Bit gelöscht. Nennen wir die Variable mal "TimeFlag"
Code:
   ....                                  ; Programmschleife
   LDS   Temp, TimeFlag
   ANDI  Temp, 0b00000001
   BREQ  Weiter
   RCALL Zeit_1
Weiter:
...


;Zeitbearbeitung
Zeit_1:
   ....
   ....
  LDS  Temp. TimeFlag
  ANDI Temp, 0b11111110     ; Zeitflag löschen
  STS   TimeFlag, Temp
RET

Auf diese Weise kannst du verschiedene Zeiten bearbeiten, ohne das dein Programm durch Delais oder Waits ausgebremst wird.
Gruß oldmax
 
.

So wie dein Hauptrogramm im Moment abläuft, brauchst du eigentlich nur ein Delay, welches sich über einen Parameter konfigurieren läßt. Das Delay "verbraucht" zwar Machinenzyklen, aber das ist durchaus sinnvoll, wenn du nicht zwischenzeitlich andere Sachen erledigen möchtest. Anstelle des Delays kannst du die Pause natürlich auch mit dem Timerinterrupt erzeugen. Dirk :ciao:

Hallo Dirk,
so in etwa lief ja das Programm wie am Anfang des Threads dargestellt.
Aus den Vorschlägen von LotadaC und Oldmax erkenne ich, daß hier viele Wege nach Rom
führen.
Ich habe diese erst mal ausgedruckt und werde versuchen, es zu verarbeiten.

Meinen Dank erst mal an Euch "es dauert eben in diesem Alter a bissel länger".

Lass von mir hören sobald ich ein lauffähiges Programm habe.
Das Buch ist angekommen und wird mich unterstützen.

Grüße

Rolf
 
Hallo LotadaC,
habe mir erst mal Deinen Vorschlag genommen, Buch daneben, wo die Befehle
wie z.B "cpi und brsh" ausführlich mit kurzen Code-Abschnitten dokumentiert werden.
Beim ersten Test wollte PB0 nicht dunkel werden.
Ich dachte, da gehört ja zum Schluß nochmal ein Zeitablauf von 5 Sec. hinein.
Also habe ich zu Beginn die Konstante von 92 auf 115 gesetzt.
Um push und pop hab ich mich erst mal noch nicht gekümmert, evtl. könnte ich
das mit r2 ganz weglassen.
Das Buch weist darauf hin, daß die Arbeitsfrequ. beim Tiny2313 bei internen 8MHz
liegt, die auf 1MHz runter geteilt wird.
Ich hatte 1,2MHz berechnet, so daß ich auf Konst. von 23 kam, bei 1MHz wären
es nur 19.
Spielt hier aber keine Rolle, es geht erst mal nur ums Prinzip.
Zu Oldmax...habe mir das FAQ mal vorgenommen und "keine Angst vor Ass."
durchgelesen...habe wieder einiges gelernt.
So, und hier der neue Quell-Code:
Code:
.INCLUDE   "tn2313def.inc"   
           
           .def    akku=r16
           .def    temp1=r17
            
           rjmp    reset           ; Reseteinsprung
           .ORG    OVF0addr        ; Interrupt-Vektor
           rjmp    TIMER0_OVF      ; Sprung zur ISR


reset:     ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0)  
           out     DDRB,akku ;Datenricht. PB0 - PB2=Output
           

; Timer0 initialisieren:
           ldi     akku,(1<<CS02)|(1<<CS00)  ;Pr.= 1024
           out     TCCR0B,akku
           ldi     akku,(1<<TOIE0) ;Register TIMSK (Bit1=1)
           out     TIMSK,akku
           sei                     ;Timer frei

loop:      
           ldi     temp1,115     ;hex=0x=73,bin.0111 0011
           ldi     akku,(1<<PB2)|(1<<PB1)|(1<<PB0)       
           out     PORTB,akku    ;PB0 - PB2 = High
           
gehe01:    cpi     temp1,92      ;vergleiche r17mit Konst.92
;wenn temp1=92 werden Bit 0-5 im Status-Register (SREG) zu
;Null gesetzt.       
           brsh    gehe01        ;Sprung nach gehe01, wenn
;Status-Bit0 (C=Carry) ungleich=High
           
           ldi     akku,(1<<PB1)|(1<<PB0)       
           out     PORTB,akku    ;PB0 - PB1 = High
           
gehe02:    cpi     temp1,69      ;hex=0x45, bin.0100 0101      
           brsh    gehe02
           cbi     PORTB,PB1
           
gehe03:    cpi     temp1,46      ;hex=0x2E, bin.0010 1110    
           brsh    gehe03
           cbi     PORTB,PB0
          
gehe04:    cpi     temp1,23      ;hex=0x17, bin.0001 0111
           brsh    gehe04        ; PB0 - PB2 = LOW
           rjmp    loop          ;Pause ca. 5 Sec.


;Interrupt-ISR

TIMER0_OVF:push    r2        ;Kopie r2 auf den Stack, danach SP-1
           in      r2,SREG   ;Inhalt vom Statusregister in r2 laden
           dec     temp1     ;Dekrement (r17-1)
           brne    weiter    ;nach weiter, wenn r17 nichtnull
           ldi     temp1,115  ;lade r17 mit 115 
weiter:
           out     SREG,r2   ;Inhalt von r2 ins SREG laden
           pop     r2        ;SP+1, danach vom Stack in r2 laden
           reti
          .EXIT

Grüße

Rolf
 
Im Auslieferungszustand ist der Tiny2313 auf den internen RC-Oszillator mit 8MHz eingestellt (CKSEL). ALLERDINGS ist auch die CKDIV8-Fuse aktiv, d.h. dieser Takt wird durch 8 dividiert - effektiv läuft der also mit 1MHz.

Zum Programm hat mein Sohn (7) Dir mal ein paar Bilder gemalt (unter meinem Diktat;)).
Das ist Dein letzter Entwurf:
clockint1.png
Erkennbar ist, daß Temp1<23 quasi keinen Effekt hat, da Du in es in der loop dann sofort auf 115 setzt - hattest Du ja oben bereits angedeutet. Außerdem wartest Du ja immer noch die vollen 5 sec (nein-Schleifen bei den Verzweigungen). Ich meinte den generellen Ablauf eher so:
clockint2.png
Edit: Außerdem würden da auch kurze high-Pulse auf den falschen Beinchen ankommen nö, stimmt doch so...
Könnte man jetzt noch etwas optimieren, zB so:
clockint3.png
vor dem rjmp loop könnte jetzt noch irgendwelcher weiterer code kommen, der dann eben auch innerhalb der 5 sec, die Dein code warten läßt ausgeführt werden würde. Wenn da kein weiterer code kommt, kannst Du natürlich direkt nach dem jeweiligen Umschalten der Beinchen nach loop springen lassen - aber so ist es halt für Erweiterungen flexibler..
 
Hi LotadaC,
danke auch an Deinen Sohn!
Habe mir clockint3 ausgedruckt und es dahin umpusseln.
Werde jetzt hierbei die Konstante 19 nehmen.

Grüße

Rolf
 
Dann mußt Du natürlich auch die anderen Konstanten mit anpassen, klar?
am sinnigsten sollte man am Anfang also folgendes vereinbaren:
".equ fuenfsekunden = 19"
Und dann im Programm statt der 19 (bisher 23) die definierte Variable einsetzen (n*fuenfsekunden, mit n=1,2,3,4). Beim assemblieren erzeugt das Studio dann die entsprechenden Zahlen aus den Termen mit der/den Konstanten, Du kannst also schnell mal da oben die Konstante ändern (und neu assemblieren/flashen), ohne das jetzt überall im code neu eintippen zu müssen - genau dazu sind die Konstanten ja da.

Edit: nur nochmal der Vollständigkeit halber: das/die Bild/er da oben ist nur die main...loop. Initialisierung (nach dem Programmstart) war ja soweit klar, das dekrementieren des Zeitzählers (temp1) bzw dessen Zurücksetzen, erfolgt weiterhin in der ISR des Timerüberlaufes (8bit-Timer mit max Vorteiler).
 
Hi
Ich bin mir jetzt nicht sicher, ob der Timer 0 auch den CTC Mode mit Interrupt bei Timer Compare kann, aber es macht keinen Sinn, eine Schleife zu programmieren, wo der Programmablauf gestört wird. Einfaches Beispiel:
Irgendwann einmal möchtest du einen Taster einlesen. Kontaktsignale sind in der Regel Signale, die man nicht über einen Interrupt bearbeitet, sondern pollt. Sie stehen lang genug an, um in jedem Programmzyklus erfasst zu werden. Nun hast du in deinem Programm aber eine Schleife, die „wartet“, das ein Wert erreicht wird. Schön, nun ist diese Denkweise mit dem Einlesen von Tastereingängen oder anderen Kontakten falsch, denn es ist nicht mehr sichergestellt, das ein Signal erfasst wird, weil das Programm in einer Programmschleife mit „Nichtstun“ beschäftigt war. Dann kommt man auf die Idee, auch solche Eingänge mit einem Interrupt einzulesen….. na toll… aber die Reaktion auf die Eingabe läßt dann auf sich warten, oder deine berechnetet Zeit stimmt plötzlich nicht mehr. LotadaC hat versucht, dir das grafisch mal aufzuzeigen. Genau das solltest du dir auch mal vornehmen, deine Gedanken in solche Ablaufstrukturen zu skizzieren. Du solltest versuchen, eine Timer-ISR aufzubauen, die in einem konstanten Zeitfenster von 1 mSek. Aufgerufen wird. Das geht im CTC Mode mit der beschriebenen Initialisierung. Zumindest bei Timer 1.
Wenn du dieses Ereignis dann bekommst, kannst du im einfachsten Ansatz daraus eine Zehntelsekunde bilden und dir ein Bit setzen. Und nun rechnen wir mal ein klein wenig:
Wenn in deiner Programmschleife 1/10 tel Sekunde erfasst werden soll und dir 1 MHz Takt den Controller treibt, dann kannst du in dieser Zeit ca. 3000 Befehle bearbeiten.
Warum: Ein Befehl braucht zwischen 1 und 4 Takten lt. Datenblatt. Nehmen wir an, im Mittel sind es 3 Takte. Die Zeit für einen Takt ist 1/f also 0,000001 Sek bei 1 MHz. Also braucht ein Befehl ca. 0,000003Sek.
Wenn ich nun eine Programmschleife habe, in der ich ein Signal, was in einer 1/10 Sek. Auftritt erfassen möchte, darf das Programm auch nicht mehr Zeit beanspruchen. Nun wirst du sagen: „ oooch, soviel Code schaff ich nie……“
Hier mal ein dickes „ACHTUNG“
Bedenke, das Befehle in Schleifen auch mehrfache Zeit benötigen und wenn du z. B. einen Blockmove programmierst, um 100 Byte in den EEProm zu kopieren, dann ist das auch die Zeit für 100 * Anzahl der Befehle in der Übertragungsschleife.
Daher ist es wichtig, grad wenn man sich mit Assembler beschäftigt, auf einen strukturierten Programmcode zu achten. Der PAP von LotadaC ist da bezeichnend für. Willst du dein Programm erweitern, dann löst du alles, was zwischen Loop und RJmp steht in einer eigenen Unteroutine. Dann steht in der Programmschleife halt nur
Code:
Loop:
   RCALL Auswerten      ; erlaubt, mit einer schmalen Struktur zu arbeiten
RJmp Loop      

Auswerten:                ;eigene Routine
   LDS   R16, Wert
   CPI R16,  Wert1
   BRNE  Chk_Wert2
   RCALL Set_All          ; alternativ kann natürlich auch hier direkt gearbeitet werden
   RJMP  End_Auswerten
Chk_Wert2:
   CPI   R16, Wert2
   BRNE  Chk_Wert3
   RCALL ...                ; alternativ kann natürlich auch hier direkt gearbeitet werden
   RJMP  End_Auswerten
Chk_Wert3:
   CPI   R16, Wert3
   BRNE  End_Auswerten
   RCALL ...                 ; alternativ kann natürlich auch hier direkt gearbeitet werden
End_Auswerten:
Ret

Welchen Vorrteil hat das? Nun, werden weitere Aufgaben erforderlich , einfach ein weiterer Aufruf in die Programmschleife und einen weiteren Programmblock aufrufen. Auf keinen Fall "Wartezeiten" durch Kleinstschleifen produzieren. Es geht im Programm nix weiter.
 
Genau das versuche ich ja schon seit meiner ersten Antwort hier - bisher hatte Rolf es aber immer wieder geschafft, doch immer wieder 'ne Warteschleife einzubauen. Deswegen die Bilder. Du hast ja jetzt auch nochmal darauf hingewiesen.

Allerdings finde ich die Wahl deines 1ms-Flags nicht sehr geschickt (wenn statt der 5 sek auch 4,980736sek hinreichend genau sind). Um aus den Millisekunden (unnötige Genauigkeit) auf die 5 Sekunden zu kommen, muß er zum Zählen dann auch unnötig große Werte erreichen. Für 5 Sekunden dann halt bis 5000. Das schafft ein Byte nicht mehr.
Bei der Wahl des Prescalers gilt es, einen Kompromiß zwischen der nötigen Genauigkeit, und der nötigen (zeitlichen) Reichweite des Timers zu finden. Wenn das mit reiner Hardware nicht geht, muß man mit irgendwelchen Hilfszählern/Flags nachhelfen, ich versuche dabei immer, möglichst viel durch die Hardware erledigen zu lassen, und möglichst wenig in Software zu ergänzen.

Aber wie Rolf ja schon erkannt hat - viele Wege können zumrichtigen Ergebnis führen (Warteschleifen ja letztendlich auch - nur wenn man dann später mehr einbauen will... hat ja Oldmax schon geschrieben....)
 
Status
Für weitere Antworten geschlossen.

Über uns

  • Makerconnect ist ein Forum, welches wir ausschließlich für einen Gedankenaustausch und als Diskussionsplattform für Interessierte bereitstellen, welche sich privat, durch das Studium oder beruflich mit Mikrocontroller- und Kleinstrechnersystemen beschäftigen wollen oder müssen ;-)
  • Dirk
  • Du bist noch kein Mitglied in unserer freundlichen Community? Werde Teil von uns und registriere dich in unserem Forum.
  •  Registriere dich

User Menu

 Kaffeezeit

  • Wir arbeiten hart daran sicherzustellen, dass unser Forum permanent online und schnell erreichbar ist, unsere Forensoftware auf dem aktuellsten Stand ist und der Server regelmäßig gewartet wird. Auch die Themen Datensicherheit und Datenschutz sind uns wichtig und hier sind wir auch ständig aktiv. Alles in allem, sorgen wir uns darum, dass alles Drumherum stimmt :-)

    Dir gefällt das Forum und unsere Arbeit und du möchtest uns unterstützen? Unterstütze uns durch deine Premium-Mitgliedschaft!
    Wir freuen uns auch über eine Spende für unsere Kaffeekasse :-)
    Vielen Dank! :ciao:


     Spende uns! (Paypal)