Hallo!
Ich versuche gerade, anhand dieses Tutorials
(http://www.mikrocontroller.net/articles/AVR-Tutorial:_Uhr) ein
Assembler-Programm für den ATmega8 zu schreiben, das eine Stoppuhr auf
einer 4-Zeichen-7-Segment-Anzeige anzeigen soll.
Allerdings bleibt dieses Programm zwischen der 9. und 10. Sekunde hängen
(vermutlich in der Schleife zum Ermitteln der Zehnerziffer des
Sekundenregisters). Sobald allerdings die nächste Minute anfängt, zeigt
das Display wieder 10 Sekunden (z.B. von 1:00 bis 1:09) die aktuelle
Zeit an, um dann beim Umschalten von 9:59 auf 10:00 wieder (allerdings
für 50 Minuten) hängen zu bleiben. Ist die Stunde vorbei, werden wieder
(mit den genannten Aussetzern) die ersten 9 Sekunden der ersten 9
Minuten angezeigt.
Während das Programm hängt, wird auf der Anzeige nur die 9 als 4. Ziffer
angezeigt (um eine Zahl anzuzeigen, zeige ich normalerweise die erste
Ziffer an, warte 2,5ms, zeige die zweite Ziffer an, usw.).
Hat irgendwer von euch eine Idee, wo der Fehler liegt, der zu dieser
Endlosschleife führt (wobei allerdings ein Interrupt den µC aus der
Schleife holt - was ja normal ist - und außerdem die normale
Funktionalität wiederherstellt - was ich mir nicht erklären kann)?
Hier ist mein aktueller Quelltext (ich habe mich allerdings nicht um das
Hinzufügen sinnvoller und Entfernen sinnloser Kommentare gekümmert, die
Kommentare sind also nutzlos):
Schau mal hier:
Beitrag "Re: 7 Segmentanzeige falsch eingekauft, reagiert nur auf low"http://www.mikrocontroller.net/attachment/23620/eieruhr1.asm
Achte besonders darauf, wie die Umwandlung Ziffer->Segment-Bitmuster
erfolgt. Das löst zwar Dein Problem noch nicht, versetzt Dich aber in
die Lage, etwas effizienteren Code zu schreiben, in dem es auch Spaß
macht, einen Fehler zu suchen.
Und gib Dir bitte auch etwas mehr Mühe mit den Kommentaren. Es macht
keinen Spaß, in einem nicht bzw. falsch kommentierten Programm Fehler zu
suchen. Meine eigene Erfahrung sagt mir, dass man viele Fehler bereits
(selbst) dadurch findet, dass man das Programm kommentiert, also dass
man den Programmablauf mit verständlichen Worten beschreibt.
...
zehner:
ldi temp1, 0x00
ldi temp2, 0x00
mov temp1, Minuten
subi temp1, 10 ; abzählen wieviele Zehner in
brcs einer ; der Zahl enthalten sind
inc temp2
rjmp zehner
Wenn du mehr als 9 Minuten hast, erhöhst du temp2 und springst nach
zehner, dadurch setzt tu temp1 wieder auf den Anfangswert und hast eine
Endlos Schleife.
Eher so was:
zehner:
ldi temp1, 0x00
ldi temp2, 0x00
mov temp1, Minuten
zehner_loop:
subi temp1, 10 ; abzählen wieviele Zehner in
brcs einer ; der Zahl enthalten sind
inc temp2
rjmp zehner_loop
Nach weiteren Schleifen habe ich nicht gesucht.
mfg
SH
ES FUNKTIONIERT!
Das Problem war tatsächlich die Endlosschleife, die brumbaer entdeckt
hat.
Ich habe das Setzen der Register temp1 und temp2 jetzt vor den Aufruf
von zehner bzw. von zehnerb geschrieben (und den Code ansatzweise
kommentiert) und jetzt funktioniert die Stoppuhr.
Vielen Dank für deine Hilfe, brumbaer.
Den Vorschlag von Hannes werde ich vielleicht beim Verbessern des Codes
(nach den Berechnungen aus dem Tutorial geht die Stoppuhr um 1,7% vor,
das lässt sich sicher noch verbessern) umsetzen.
Hier noch der korrigierte Code:
1
.include "m8def.inc"
2
3
.def temp1 = r16
4
.def temp2 = r17
5
6
.def SubCount = r21
7
.def Sekunden = r22
8
.def Minuten = r23
9
10
.def digit1 = r25
11
.def digit2 = r26
12
.def digit3 = r27
13
.def digit4 = r28
14
15
.org 0x0000
16
rjmp main ; bei Reset zum Hauptprogramm springen
17
.org OVF0addr
18
rjmp timer0_overflow ; bei Timer-Overflow zur Behandlungsroutine springen
Hi
Du hast eine schöne große Programmschleife. Da was zu ändern oder u
erweitern wird schwierig und irgendwann verlierst du den Überblich.
Daher empfehle ich dir viel mehr Variable einzusetzen und mehr
verteilung auf Unterprogramme. Sind diese in sich getestet, kannst du
sie abhaken. Bei einer Fehlersuche wirst du dankbar sein, nicht immer
den ganzen Code durchsehen zu müssen. Hier mal ein Vorschlag, ist aber
nur ein grobes Gerüst:
Ich hab das bei meinem Atmega8 bei 8MHz folgendermaßen gemacht:
1
.DEF ms0 = R4 ; Millisekunden einer
2
.DEF ms1 = R5 ; Millisekunden Zehner
3
.DEF ms2 = R6 ; Millisekunden Hunderter
4
.DEF Sekunden = R7 ; Sekunden
5
.DEF Minuten = R8 ; Minuten
6
.DEF Stunden = R9 ; Stunden
7
8
.DSEG
9
ms0_Var: .Byte
10
ms1_Var: .Byte
11
ms2_Var: .Byte
12
Sek_Var: .Byte
13
Min_Var: .Byte
14
Std_Var: .Byte
15
Segment0: .Byte ; Aufnahme vom Bittmuster für die 7 Segmente
16
Segment1: .Byte ; zuordnen in einer Initialisierung
17
Segment2: .Byte ; in Unterprogramm Set_Segment wird aus einer
18
Segment3: .Byte ; Zahl das Segmentmuster in Anzeige kopiert
19
Segment4: .Byte ; welche Zahl dargestellt werden soll steht in
20
Segment5: .Byte ; der Variablen Anz_Nr
21
Segment6: .Byte
22
Segment7: .Byte
23
Segment8: .Byte
24
Segment9: .Byte
25
Anzeige: .Byte ; Anzuzeigendes Bitmuster
26
Anz_Nr: .Byte ; Bit Nummer der anzuzeigenden Stelle
27
Gem_Anz: .Byte ; Bit wird geschoben und steuert Anode oder Kathode
Danach am Anfang meines Programmes, nicht in der Programmschleife,
sondern direkt nach der Stack-Initialisierung das UP für die
Parametrierung des Timers 1 aufgerufen.
.CSEG
1
Start: ..... ; Stack setzen
2
RCALL Init_Timer1 ; Timer 1 initialisieren
3
weitere Initialisierungen
4
Loop: Read_Port ; Programmschleife beginne mit Eingaben lesen
5
....
6
Set_Segment ; Bitmuster für 7Segment setzen
7
Write_Port ; Ausgänge schreiben
8
RJMP Loop
9
10
; --------- Bereich Unterprogramme -----------
11
12
;-----------Timer 1 Parametrierung -----------
13
;----------aus dem Tutorial angepaßt----------
14
Init_Timer1: LDI Temp_Reg, high( 8000 - 1 )
15
OUT OCR1AH, Temp_Reg
16
LDI Temp_Reg, low( 8000 - 1 )
17
OUT OCR1AL, Temp_Reg ; CTC Modus ; Vorteiler auf 1
Ich habe mal nicht allzuviele Komentare stehen lassen, aber diese ISR
zählt die Zeit. Um eine Stoppuhr draus zu machen, kannst du dir ein Bit
irgendwo setzen und in der ISR abfragen. Nenn die Bytevariable Time_Ctrl
dann ergänzt du z. B: in der ISR folgendermaßen:
am Anfang
Um eine Möglichkeit zur Anzeige von Zwischenwerten zu haben, kopierst du
am Ende der ISR die Zeitwerte in Variablen
Vorher fragst du aber dein Kontrollbyte, ob der Zähler laufen, oder ob
die Anzeige stehen bleiben soll
STS ms0_Var, ms0 ; Registerwerte in Variablen schreiben
5
STS ms1_Var, ms1
6
STS ms2_Var, ms2
7
STS Sek_Var, sekunden
8
STS Min_Var, minuten
9
STS Std_Var, Stunden
10
End2_Isr: POP ….
Wenn du nun in einer der ms den Aufruf zur Anzeige bringst und dort
entsprechend durchzählst, das Bit für die Stelle schiebst und die
Ausgabe an die Anzeige schickst, steht die Anzeige lange genug, damit
sie erkennbar ist. Willst du diese Werte nun auch mal an einen PC
schicken, so greifst du auf die Variablen zu. Um das Programm zu
kontrollieren, kannst du über RS232 und PC mein hier veröffentlichtes
OpenEye nutzen. Es zeigt dir die Werte im µC in den Variablen sind. Ist
unter dem Thread "UART mit atmega 16" zu finden.
Ich hoffe, dir weiter geholfen zu haben.
Gruß oldmax