Hallo zusammen,
will mit einem 89C5131 eine Stoppuhr bauen.
Hänge jetzt aber schon beim Hochzählen der 7 Segment-Anzeige, die im
Multiplex-Betrieb läuft.
Im Debuger scheint alles in Ordnung zu sein, sobald ich überspiele zählt
er zwar, aber zeigt komische Zahlen an (vermutlich irgendwas mit dem
dptr)
Grüße
hier der Code
"org 0000h
jmp Hauptprogramm
Hauptprogramm:
mov dptr, #tab //7Segment Tabelle mit dptr verknüpfen
mov R0, #00000000h //Initialisieren der 4, 7Seg-Anzeigen
mov R1, #00000000h
mov R2, #00000000h
mov R3, #00000000h
m2:
call Ausgabe //Ausgabe aufrufen
cjne R0, #00001010b, m1 //Abfrage ob schon bis 10 Sec gezält
wurde
mov R0, #00000000b //Es wurde bis 10 Sec gezält, wieder bei 0
Sec anfangen
inc R1 //10er Sec 1 Hochzählen
cjne R1, #00000110b, m2 //Abfrage ob schon 60 Sec erreicht wurden
call Ausgabe
mov R1, #00000000b //Es wurde bis 60 Sec gezält, wieder bei 0
Sec anfangen
inc R2 //1 Min hochzählen
cjne R2, #00001010b, m2 //Abfrage ob schon bis 10 Min gezält
wurde
call Ausgabe
mov R2, #00000000b //Es wurde bis 10 Min gezält, wieder bei 0
Min anfangen
inc R3 //10er Min 1 Hochzählen
cjne R3, #00000110b, m2 //Abfrage ob schon 60 Min erreicht wurden
call Ausgabe
mov R3, #00000000b //Es wurde bis 60 Min gezält, wieder bei
komplett 0 Anfangen
jmp Hauptprogramm
m1: inc R0 //1 Sek wird hochgezält
jmp m2
Ausgabe:
mov A, R0
mov R4, A
mov A, R1
mov R5, A //Sichern der Zählregister
mov A, R2
mov R6, A
mov A, R3
mov R6, A
mov A, R4 //Register in den Akku, damit die gewünschte
Stelle mit dem dptr ausgewählt werden kann
movc A, @A+dptr //Auswählen der Stelle mit dem dptr in der
Tab für 7-Seg. Code
mov P2, A //7Seg. Code in die 7-Seg-Anzeige schreiben
mov P3, #00001110b //Richtige 7-Seg-Anzeige auswählen (hier 1er
Sec.)
call wait //Warteschleife
mov A, R5
movc A, @A+dptr
mov P2, A
mov P3, #00001101b
call wait
mov A, R6
movc A, @A+dptr
mov P2, A
mov P3, #00001011b
call wait
mov A, R7
movc A, @A+dptr
mov P2, A
mov P3, #00000111b
call wait
ret
wait:
push 00h
mov R0, #0FFh
n1: dec R0
cjne R0, #0FFh, n1
mov R0, #0FFh
n2: dec R0
cjne R0, #0FFh, n2
mov R0, #0FFh
n3: dec R0
cjne R0, #0FFH, n3
pop 00h
ret
tab:
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b, 01101101b,
01111101b
db 00000111b, 01111111b, 01101111b
end"
Florian K. schrieb:
> Hänge jetzt aber schon beim Hochzählen der 7 Segment-Anzeige, die im> Multiplex-Betrieb läuft.
Damit das Multiplexen richtig funktioniert, muß man den Timerinterrupt
verwenden.
Es programmiert sich einfacher, wenn man die Register als
Arbeitsregister verwendet (Scratchpad-Register).
D.h. globale Variablen legt man im RAM an.
Und die Register benutzt man nur lokal, d.h. man muß sie nicht sichern.
> Im Debuger scheint alles in Ordnung zu sein, sobald ich überspiele zählt> er zwar, aber zeigt komische Zahlen an (vermutlich irgendwas mit dem> dptr)
"Komisch" ist keine sinnvolle Feherbeschreibung.
Teile und herrsche:
Wenn das Anzeigen nicht funktioniert, dann laß erstmal das Zählen weg.
Peter
ok, danke schonmal, werde es gleich mal mit einem Timerinterrupt
probieren.
kann ich Werte einfach irgendwo in den Ram schreiben?
Bsp.
mov R0, #0FFh
mov 70h, R0 ?
mov A, 70h
und später dann abrufen?
Komme ich mir da nicht mit den verschiedenen Registern in die Quere?
Wegen dem "komisch".
Manchmal zählt er richtige Zahlen, dann aber nur noch unvollständige
Zahlen (Es fehlen ein paar "Striche" vom Segment(glaube digits war hier
die richtige Bezeichnung))
Florian K schrieb:
> ok, danke schonmal, werde es gleich mal mit einem Timerinterrupt> probieren.>> kann ich Werte einfach irgendwo in den Ram schreiben?
Gib ihnen besser Namen und laß sie vom Assembler im Datensegment
plazieren:
wenn ich das jetzt richtig verstanden habe, kann ich mit dem Befehl DS,
automatisch einen Platz im Speicher freihalten?
Quasi neue variable Register erschaffen?
Bei Keil im Debugger bringt er mir folgende Fehlermeldung:
*** error 65: access violation at C:0x0002 : no 'execute/read'
permission
ich vermute, das irgendwas mit dem org nicht stimmt, weiß aber nich
warum und wie ich den Fehler beheben kann.
Im Anhang mein geschriebenes Programm
Grüße
Florian
Deinen Anhang hab ich mir momentan noch nicht angesehen, ich konzentrier
mich erstmal auf das, was du direkt angegeben hast.
> call loop //Endlosschleife
Oh oh... Ich empfehle Grundlagenstudium. Lies dir bitte die
Befehlsbeschreibung durch. Ein CALL braucht immer einen RETurn, da der
CALL die Adresse des nachfolgenden Befehls auf den Stack sichert. Der
RETurn holt die Adresse vom Stack und setzt das Programm an der Adresse
fort. In deinem Fall sicherst du munter auf den Stack, aber holst nix
ab. Resultat ist, dass irgendwann deine Variablen überschrieben werden,
wenn der Stack von 0xFF nach 0x00 überläuft -> Das heisst, du musst den
CALL durch einen JMP ersetzen. Der JMP springt nur an die angegebene
Adresse, ohne den Stack zu modifizieren.
Wie Peter bereits geschrieben hatte, ist die Verwendung der Register für
die Variablen ungünstig. Erstens weil man sie eher als Arbeitsregister
denn als Variablen verwendet, zweitens weil du nur acht pro Bank hast,
und drittens, weil du viel ändern musst, wenn dir die acht nicht mehr
reichen :)
Wie bereits gesagt wurde, bietet sich die Verwendung des allgemeinen
RAMs an.
1
dseg at 0030h //Datensegment nach dem bitadressierbaren Bereich
2
var1 ds 1 //1-Byte Variable
3
var2 ds 1 //1-Byte Variable
4
var3 ds 2 //2-Byte Variable
5
...
6
7
cseg
8
org 0000h
9
jmp Main //Freimachen der Interrupt-Einsprungadress
10
...
Ich hab die Syntax jetzt nicht nachgeprüft, aber du solltest das
hinbekommen, denke ich. Wenn das alles flutscht, poste mal das neue
Programm, als komplettes uV3-Projekt.
Ralf
oh man, das hätte mir selber Einfallen können mit dem call Loop, habe
wohl einen schwierigen Fehler gesucht und den leichten übersehen. Danke
dir, werde das Programm jetzt noch mal umschreiben.
Gruß
So, habe jetzt mal die Variablen in den Ram geschrieben und die
Endlosschleife umgeschrieben.
Jetzt zählt er jede Sekunde hoch, gibt jedoch immer noch falsche Zahlen
aus.
bei den Einern:
0
-> falsche Zahl (C spiegelverkehrt?)
-> 2
-> 3
-> falsche Zahl (9?)
-> falsche Zahl (9?)
-> falsche Zahl (8?)
-> falsche Zahl (C spiegelverkehrt?)
-> 8
-> 9
fängt wieder von vorne an
bei den Zehnern:
0
-> falsche Zahl (C spiegelverkehrt?)
-> 2
-> 3
-> falsche Zahl (9?)
-> falsche Zahl (9?)
-> 0
desweiteren flackern die ersten 3 7-Segment anzeigen.
1
//Florian Krämer
2
//Projekt Stoppuhr
3
4
org0000h
5
6
jmpMain//Freimachen der Interrupt-Einsprungadress
7
8
org000Bh//Einsprungadresse Timer0-Interrupt
9
10
ljmpISR_Timer0
11
12
org001Bh//Einsprungadresse Timer1-Interrupt
13
14
ljmpISR_Timer1
15
16
17
org0100h
18
19
Main:
20
21
22
callinit//Initialisierung der verwendeten Register
23
calltimer_cfg//Timer konfigurieren
24
callram_belegen//Belegen des Rams mit den Zählvariablen
25
loop:
26
27
callAusgabe
28
29
calltime//Unterprogramm zur Zeitberechnung aufrufen
30
31
jmploop//Endlosschleife
32
33
34
ISR_Timer0://Interrupt-Service-Routine Timer0
35
36
movTL0,#0B0h//Timer0 vorladen
37
movTH0,#3Ch//65536-50000=15536 --> 3CB0h (50ms)
38
movR0,#1//Freigabe zur Zeitberechnung
39
reti
40
41
42
ISR_Timer1://Interrupt-Service-Routine Timer1
43
44
movTL1,#078h//Timer1 vorladen
45
movTH1,#0ECh//65536-5000=60536 --> EC78h (5ms)
46
movR2,#1
47
48
reti
49
50
init://Initialisierung der verwendeten Register
51
52
movR0,#0//Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
53
movR1,#0//"Zählfreigabe" durch den Interrupt
54
movR2,#0//Ausgabefreigabe durch den Interrupt
55
movsec_one,#0//Segment rechts (Sekunden Einer)
56
movsec_ten,#0//Segment Mitte recht (Sekunden Zehner)
57
movmin_one,#0//Segment Mitte links (Minuten Einer)
So, gugge mol do:
http://www.atmel.com/dyn/resources/prod_documents/doc0509.pdf
Dann legen wir mal los:
> org 0100h>> Main:
Den org kannste nach der letzten verwendeten ISR-Einsprungadresse
rauswerfen, spart Speicherplatz.
> call ram_belegen //Belegen des Rams mit den Zählvariablen> ...> ram_belegen:>> dseg at 0030h>> sec_one: ds 1> sec_ten: ds 1> min_one: ds 1> min_ten: ds 1>> cseg>> ret
Kann komplett, das hast du falsch verstanden, hier würde nur das RET
ausgeführt werden. Die DS-Angabe ist für den Assembler, NICHT für den
Controller, d.h. hier sagst du dem Assembler, dass es ein Datensegment
ab Adresse 0x0030 gibt, in dem du die darauf mit DS deklarierten
Variablen (genauer gesagt Anzahl Bytes mit jeweiligem Namen) ablegen
möchtest. Daraus folgt, dass der Assembler einfach beim Übersetzen in
Maschinencode folgende Adresszuweisungen macht:
sec_one -> 0x0030
sec_ten -> 0x0031 (= Adresse von sec_one + 1)
...
Üblicherweise steht der DSEG-Abschnitt in jeder Datei noch vor dem
ersten Befehl. Somit sieht man direkt am Anfang der Datei, ob Speicher
reserviert wird (anstatt zu denken, dass das Modul keinen Speicher
braucht und mittendrin sieht man's dann). Das heisst, RAM_BELEGEN ist
kein Unterprogramm, sondern eben eine Anweisung für den Assembler, an
welchen Adressen er die Variablen speichern soll.
Dann zu den Timern allgemein, du solltest den Timer prinzipiell vor dem
Neuladen der Timerregister anhalten. Das macht man vor allem deswegen,
um fehlerhaftes Neuladen zu vermeiden. Beispiel: Wenn der Reloadwert,
bei dem das Lowbyte nahe 0x00 ist, mit dem Lowbyte zuerst geladen wird,
was passiert dann? Nehmen wir an, das Lowbyte des RL ist tatsächlich
0x00. Du lädst das Lowbyte zuerst, dann das Highbyte. Das Lowbyte wird
aber schon dekrementiert, während du das Highbyte schreibst. Resultat
ist, dass der Zähler um 255 zu groß ist. Hoffe, das war verständlich :)
Dein Programm dürfte momentan so gut wie gar nicht laufen, da R0 und R2
in den jeweiligen ISRs auf 0x01 gesetzt werden, dieser Wert aber
nirgendwo behandelt wird, soweit ich das sehen kann.
Bringen wir also etwas Übersicht rein:
1. Knick einen der beiden Timer-Interrupts, vorzugsweise Timer 1, also
mit Timer 0 weiterarbeiten. Timer 1 wird in den Standard-8051ern
verwendet, um die Baudrate für den UART zu generieren, er bleibt dir
somit frei. Je nach verwendetem Derivat gibt es sogar dedizierte
Baudratengeneratoren, die auch Timer 1 für andere Sachen freilassen.
Dein Derivat dürfte sowas ebenfalls haben, aber es geht wie gesagt auch
mit einem Timer.
2. Soweit ich sehen kann, ist deine kleinste Einheit die Sekunde,
richtig? Also verwende den Reload für 50ms, das passt schon mal.
3. Verwende eine zusätzliche Variable um die 50ms-Einheiten zu zählen.
Die Variable wird auf 20 (= 20 x 50ms = 1s) initialisiert.
4. Im Timer-Interrupt wird die Variable mit dem DJNZ-Befehl abgefragt:
1
djnz VAR, T0ISRE //Variable dekrementieren und abfragen
2
//Ist die Variable ungleich 0, Sprung auf Ende
3
...
Ist die Variable in einem der Durchläufe gleich 0, gehts in der ISR bei
den drei Punkten (s.o.) weiter: Die Variable wird frisch auf 20 gesetzt
und du inkrementierst sec_one. Ist sec_one ungleich bzw. kleiner als 60,
springst du zum Ende (CJNE sec_one, #60, T0ISRE), ansonsten ist der
nächste Befehl das Löschen von sec_one und das Inkrementieren von
sec_ten. So spielt sich das ganze ab, bis du durch alle Werte durch
bist. Der jeweilige Wert wird nur dann bearbeitet, wenn das
Inkrementieren des vorhergehenden Wertes dies erforderlich macht.
Du kannst eine weitere Variable (geschickterweise vom bit-Typ, für den
Anfang darfs ausnahmsweise vom Typ byte sein) verwenden, um nach jeweils
20 Durchläufen dem Hauptprogramm zu signalisieren, dass eine Sekunde rum
ist und ein Update der Anzeige durchgeführt werden soll.
Hoffe, das war soweit verständlich und nicht zuviel auf einmal. Wenn
möglich, könntest du einen Schaltplan posten, dann kann man noch besser
helfen.
Ich bin mal kurz weg, schau aber nachher nochmal rein...
Ralf
Ralf schrieb:
> Den org kannste nach der letzten verwendeten ISR-Einsprungadresse> rauswerfen, spart Speicherplatz.
wird gemacht, ist auch einleuchtent.
>> call ram_belegen //Belegen des Rams mit den Zählvariablen>> ...>> ram_belegen:>>>> dseg at 0030h>>>> sec_one: ds 1>> sec_ten: ds 1>> min_one: ds 1>> min_ten: ds 1>>>> cseg>>>> ret> Kann komplett, das hast du falsch verstanden, hier würde nur das RET> ausgeführt werden. Die DS-Angabe ist für den Assembler, NICHT für den> Controller, d.h. hier sagst du dem Assembler, dass es ein Datensegment> ab Adresse 0x0030 gibt, in dem du die darauf mit DS deklarierten> Variablen (genauer gesagt Anzahl Bytes mit jeweiligem Namen) ablegen> möchtest. Daraus folgt, dass der Assembler einfach beim Übersetzen in> Maschinencode folgende Adresszuweisungen macht:>> sec_one -> 0x0030> sec_ten -> 0x0031 (= Adresse von sec_one + 1)> ...>> Üblicherweise steht der DSEG-Abschnitt in jeder Datei noch vor dem> ersten Befehl. Somit sieht man direkt am Anfang der Datei, ob Speicher> reserviert wird (anstatt zu denken, dass das Modul keinen Speicher> braucht und mittendrin sieht man's dann). Das heisst, RAM_BELEGEN ist> kein Unterprogramm, sondern eben eine Anweisung für den Assembler, an> welchen Adressen er die Variablen speichern soll.
ok, wusste ich bis jetzt noch nicht, ist aber auf jeden Fall von
Vorteil, denke auch das mir die Register ausgegangen wären.
> Dann zu den Timern allgemein, du solltest den Timer prinzipiell vor dem> Neuladen der Timerregister anhalten. Das macht man vor allem deswegen,> um fehlerhaftes Neuladen zu vermeiden. Beispiel: Wenn der Reloadwert,> bei dem das Lowbyte nahe 0x00 ist, mit dem Lowbyte zuerst geladen wird,> was passiert dann? Nehmen wir an, das Lowbyte des RL ist tatsächlich> 0x00. Du lädst das Lowbyte zuerst, dann das Highbyte. Das Lowbyte wird> aber schon dekrementiert, während du das Highbyte schreibst. Resultat> ist, dass der Zähler um 255 zu groß ist. Hoffe, das war verständlich :)
jop ergibt Sinn :)
> Dein Programm dürfte momentan so gut wie gar nicht laufen, da R0 und R2> in den jeweiligen ISRs auf 0x01 gesetzt werden, dieser Wert aber> nirgendwo behandelt wird, soweit ich das sehen kann.
Programm läuft in diesem Gesichtspunkt. Die Register werden vor der
Zeitberechnung bzw. Ausgabe abgefragt.
> Bringen wir also etwas Übersicht rein:> 3. Verwende eine zusätzliche Variable um die 50ms-Einheiten zu zählen.> Die Variable wird auf 20 (= 20 x 50ms = 1s) initialisiert.
müsste im mom Register 0 sein.
> 4. Im Timer-Interrupt wird die Variable mit dem DJNZ-Befehl abgefragt:>
1
> djnz VAR, T0ISRE //Variable dekrementieren und abfragen
2
> //Ist die Variable ungleich 0, Sprung auf Ende
3
> ...
4
>
>> Ist die Variable in einem der Durchläufe gleich 0, gehts in der ISR bei> den drei Punkten (s.o.) weiter: Die Variable wird frisch auf 20 gesetzt> und du inkrementierst sec_one. Ist sec_one ungleich bzw. kleiner als 60,> springst du zum Ende (CJNE sec_one, #60, T0ISRE), ansonsten ist der> nächste Befehl das Löschen von sec_one und das Inkrementieren von> sec_ten. So spielt sich das ganze ab, bis du durch alle Werte durch> bist. Der jeweilige Wert wird nur dann bearbeitet, wenn das> Inkrementieren des vorhergehenden Wertes dies erforderlich macht.> Du kannst eine weitere Variable (geschickterweise vom bit-Typ, für den> Anfang darfs ausnahmsweise vom Typ byte sein) verwenden, um nach jeweils> 20 Durchläufen dem Hauptprogramm zu signalisieren, dass eine Sekunde rum> ist und ein Update der Anzeige durchgeführt werden soll.
soweit auch verständlich, habe nur mal irgendwo gelesen, dass man in der
ISR nicht soviel Zeug ausführen sollte, deswegen habe ich dort nur die
Freigabe für Berechnung und Ausgabe gemacht.
Also, auf zur nächsten Runde :)
Frage zum Schaltplan: Die Steckverbinder sind mit P1 und P2 beschriftet,
die Daten laufen über einen Bus, der mit PA0..7 bezeichnet ist, und die
Software verwendet P3, um die Segmente zu steuern... -> ???
Wat denn nu? :)
1
dseg at 0030h
2
3
sec_one: ds 1
4
sec_ten: ds 1
5
min_one: ds 1
6
min_ten: ds 1
7
8
twenty_ms: ds 1
9
out: ds 1
10
11
cseg
Genau diesen Abschnitt setzt du über das "org 0000h", dann passt das
wunnebar ;) Klingt zwar pingelig, aber das Mischen des Variablen- mit
dem Codebereich entfällt und dient somit der Übersichtlichkeit.
Dann sorgen wir gleich mal für ein paar Verbesserungen der
Übersichtlichkeit und Verständlichkeit:
Füge noch vor dem DSEG folgendes ein:
1
SEGO_EN EQU P3.0 ;Steuerleitung für Anzeige 0
2
SEG1_EN EQU P3.1 ;Steuerleitung für Anzeige 1
3
SEG2_EN EQU P3.2 ;Steuerleitung für Anzeige 2
4
SEG3_EN EQU P3.3 ;Steuerleitung für Anzeige 3
EQU sagt dem Assembler, dass der Begriff vor dem EQU ein weiterer Name
für den Begriff hinter dem EQU ist.
Hat den Vorteil, dass es erstens verständlicher in der Software ist, und
wenn sich mal die Pinbelegung ändert, musst du nur die vier Einträge
anpassen, anstatt überall im Programm mehrmals auszutauschen. Achtung:
Dies verweist bereits auf die Fähigkeit des 8051, die Portpins direkt
über die Bitbefehle steuern zu können! Näheres dazu weiter unten.
Das gleiche kannst du jetzt für die Ansteuerung der einzelnen LEDs eines
Segments machen (TAB entsprechend anpassen):
1
SEGCHAR0 EQU 00111111b
2
SEGCHAR1 EQU 00000110b
3
SEGCHAR2 EQU 01011011b
4
SEGCHAR3 EQU 01001111b
5
SEGCHAR4 EQU 01100110b
6
SEGCHAR5 EQU 01101101b
7
SEGCHAR6 EQU 01111101b
8
SEGCHAR7 EQU 00000111b
9
SEGCHAR8 EQU 01111111b
10
SEGCHAR9 EQU 01101111b
Ist vielleicht nicht unbedingt nötig, aber evtl. hilfreich.
Und weiter gehts...
1
SEG0_ON MACRO ;Makro für Aktivierung von Anzeige 0
2
setb SEG1 ;Segment 1 deaktivieren
3
setb SEG2 ;Segment 2 deaktivieren
4
setb SEG3 ;Segment 3 deaktivieren
5
clr SEG0 ;Segment 0 aktivieren
6
ENDM
Damit wird ein Makro definiert, welches über Bitbefehle die Anzeige 0
aktiviert. Wichtig ist die Reihenfolge, zuerst werden die anderen
Segmente deaktiviert, damit nix durcheinander gerät. Dasselbe machst du
entsprechend für die anderen Segmente. Der Aufruf erfolgt dann einfach
mit dem Makronamen (SEGx_ON). Du kannst auch Makros entsprechend fürs
Ausschalten definieren, was du aber wahrscheinlich nicht brauchen wirst
bzw. die obige Variante verhindert, dass du das Ausschalten vergisst,
kostet aber ein winziges Stück Speicher :) Das geht natürlich auch
einfach mit Unterprogrammen. Makros bieten zum einen den Vorteil, dass
sie Parameter besitzen können, und bieten sich zum andern an, wenn ein
Unterprogramm aufgrund des CALL- und RET-Befehls den Code unnötig
aufblähen würde. Ausserdem wäre es passend für deine Ausgabefunktion
(können wir erweitern, wenn das Grundprogramm funzt). Der Vorteil der
o.g. Variante mit den Bitbefehlen gegenüber deiner Variante ist, dass
die anderen vier Bits unbeeinflusst bleiben. Kann man aber auch über die
UND- bzw. ODER-Befehle realisieren:
1
; Beispiel für Segment 0
2
orl P3,#0Fh ;Alle Segment deaktivieren
3
anl P3,#0Eh ;Segment 0 aktivieren
Eine ganz andere Alternative ist, die Werte für den Port ebenfalls in
einer Tabelle abzulegen, und über eine Zählvariable das Segment zu
bestimmen. Macht aber nur Sinn, wenn alle Segmente vom gleichen Port
gesteuert werden.
Okay, damit dürfte prinzipiell schon mal die Übersichtlichkeit in deinem
Code steigen.
Weiter gehts...
- In der ISR fehlt das Stoppen des Timer vor dem Laden
- Ich habe gepennt! Das ganze läuft ja über Multiplex(stirnklopf), das
heisst, du darfst natürlich nicht nur jede Sekunde updaten, sondern
musst quasi so schnell wie möglich die Ausgabe voranbringen! -> SORRY,
Asche auf mein Haupt.
Ist aber auch kein Problem, kriegen wir hin ;)
Du musst (leider) nochmal den Timer-Reloadwert und die ISR anpassen.
50ms entsprechen 20Hz, das ist ein bisschen zu wenig, um als angenehm
empfunden zu werden. 50Hz wären besser, also ein Reloadwert für 20ms.
Die Variable fürs Zählen in der ISR entsprechend abändern auf 50.
Die Variable OUT (wird später ein Bit -> Spart Speicher) solltest du im
Unterprogramm AUSGABE auf ungleich 0 prüfen:
1
mov a,out ;Variable OUT in Akku laden
2
jz AUSGABE_ENDE ;Wenn Akku 0 ist, Sprung auf Ende
3
mov out,#0 ;OUT Variable löschen
4
...
5
AUSGABE_ENDE:
6
ret
Diese Variante lässt sich zum einen besser auf die spätere Verwendung
einer Bitvariablen anpassen und zum Anderen kehrt sie ins Hauptprogramm
zurück, wenn OUT gleich 0 ist. Der Vorteil ist momentan für dich
vielleicht nicht klar ersichtlich, er liegt darin, dass du wieder in der
Mainroutine landest, und dort weitere Unterprogramme aufrufen kannst
(beispielsweise Tastaturabfrage), anstatt in der Ausgaberoutine zu
warten (= zu hängen).
So, jetzt müsste die Ausgaberoutine genauer unter die Lupe genommen
werden:
Das von dir erwähnte Flackern, wie genau äussert sich das? Hast du das
Flackern auch, wenn du die Zeitschleife rausnimmst (die du m.E.
überhaupt nicht brauchst!)?
Was bezweckst du mit der Zeitschleife? Willst du damit sicherstellen,
dass die einzelnen Segmente lange genug an sind, um erkannt zu werden?
Soweit ich es im ersten Moment sehen kann, ist die Ausgaberoutine okay.
Was macht dich so sicher, dass die Konvertierung für die Anzeige den
Fehler verursacht?
Die geschickteste Methode, die Ausgabe in Ruhe zu prüfen wäre, den
Timer-Interrupt zu deaktivieren, in der Ausgaberoutine die Variable OUT
zu ignorieren (also Befehl auskommentieren) und einfach im Hauptprogramm
mal die Variablen sec_one, etc. mit festen Werten zu belegen. Und zwar
in zehn Durchläufen für alle Segmente die entsprechenden Ziffern, dann
müsstest du sehen, in welchem Abschnitt was klemmt. Probier das bitte
mal aus.
Ralf
SEG0_ON MACRO ;Makro für Aktivierung von Anzeige 0
2
setb SEG1 ;Segment 1 deaktivieren
3
setb SEG2 ;Segment 2 deaktivieren
4
setb SEG3 ;Segment 3 deaktivieren
5
clr SEG0 ;Segment 0 aktivieren
6
ENDM
Muss natürlich jetzt SEG0_EN <- heissen, sorry.
Ich hoffe, ich hab nicht noch was durcheinander gebracht :( Ich klopp
mich wohl besser jetzt in die Heia...
Bis morgen...
Ralf
Ralf schrieb:
> Also, auf zur nächsten Runde :)
Jo neuer Tag, neue Taten ;)
> Frage zum Schaltplan: Die Steckverbinder sind mit P1 und P2 beschriftet,> die Daten laufen über einen Bus, der mit PA0..7 bezeichnet ist, und die> Software verwendet P3, um die Segmente zu steuern... -> ???> Wat denn nu? :)
also der Schaltplan stimmt schon, nur habe ich eine µC-Platine und eine
7-Segment-Platine
auf der µC-Platine habe ich 4 Ports, die ich frei belegen kann.
auf der 7-Segment-Platine habe ich Port1 (Daten) und Port2 (Ansteuern
des einzelnen Segmentes)
werde aber einfacherweiße das Programm umschreiben dass es auf beiden
Platinen übereinstimmt.
>
1
> dseg at 0030h
2
>
3
> sec_one: ds 1
4
> sec_ten: ds 1
5
> min_one: ds 1
6
> min_ten: ds 1
7
>
8
> twenty_ms: ds 1
9
> out: ds 1
10
>
11
> cseg
12
>
> Genau diesen Abschnitt setzt du über das "org 0000h", dann passt das> wunnebar ;) Klingt zwar pingelig, aber das Mischen des Variablen- mit> dem Codebereich entfällt und dient somit der Übersichtlichkeit.
jop, dann weiß man gleich wo man dran ist.
> Dann sorgen wir gleich mal für ein paar Verbesserungen der> Übersichtlichkeit und Verständlichkeit:>> Füge noch vor dem DSEG folgendes ein:>
1
> SEGO_EN EQU P3.0 ;Steuerleitung für Anzeige 0
2
> SEG1_EN EQU P3.1 ;Steuerleitung für Anzeige 1
3
> SEG2_EN EQU P3.2 ;Steuerleitung für Anzeige 2
4
> SEG3_EN EQU P3.3 ;Steuerleitung für Anzeige 3
5
>
> EQU sagt dem Assembler, dass der Begriff vor dem EQU ein weiterer Name> für den Begriff hinter dem EQU ist.> Hat den Vorteil, dass es erstens verständlicher in der Software ist, und> wenn sich mal die Pinbelegung ändert, musst du nur die vier Einträge> anpassen, anstatt überall im Programm mehrmals auszutauschen. Achtung:> Dies verweist bereits auf die Fähigkeit des 8051, die Portpins direkt> über die Bitbefehle steuern zu können! Näheres dazu weiter unten.> Das gleiche kannst du jetzt für die Ansteuerung der einzelnen LEDs eines> Segments machen (TAB entsprechend anpassen):>
1
> SEGCHAR0 EQU 00111111b
2
> SEGCHAR1 EQU 00000110b
3
> SEGCHAR2 EQU 01011011b
4
> SEGCHAR3 EQU 01001111b
5
> SEGCHAR4 EQU 01100110b
6
> SEGCHAR5 EQU 01101101b
7
> SEGCHAR6 EQU 01111101b
8
> SEGCHAR7 EQU 00000111b
9
> SEGCHAR8 EQU 01111111b
10
> SEGCHAR9 EQU 01101111b
11
>
> Ist vielleicht nicht unbedingt nötig, aber evtl. hilfreich.
wird gemacht.
> Und weiter gehts...>
1
> SEG0_ON MACRO ;Makro für Aktivierung von Anzeige 0
2
> setb SEG1 ;Segment 1 deaktivieren
3
> setb SEG2 ;Segment 2 deaktivieren
4
> setb SEG3 ;Segment 3 deaktivieren
5
> clr SEG0 ;Segment 0 aktivieren
6
> ENDM
7
>
> Damit wird ein Makro definiert, welches über Bitbefehle die Anzeige 0> aktiviert. Wichtig ist die Reihenfolge, zuerst werden die anderen> Segmente deaktiviert, damit nix durcheinander gerät. Dasselbe machst du> entsprechend für die anderen Segmente. Der Aufruf erfolgt dann einfach> mit dem Makronamen (SEGx_ON). Du kannst auch Makros entsprechend fürs> Ausschalten definieren, was du aber wahrscheinlich nicht brauchen wirst> bzw. die obige Variante verhindert, dass du das Ausschalten vergisst,> kostet aber ein winziges Stück Speicher :) Das geht natürlich auch> einfach mit Unterprogrammen. Makros bieten zum einen den Vorteil, dass> sie Parameter besitzen können, und bieten sich zum andern an, wenn ein> Unterprogramm aufgrund des CALL- und RET-Befehls den Code unnötig> aufblähen würde. Ausserdem wäre es passend für deine Ausgabefunktion> (können wir erweitern, wenn das Grundprogramm funzt). Der Vorteil der> o.g. Variante mit den Bitbefehlen gegenüber deiner Variante ist, dass> die anderen vier Bits unbeeinflusst bleiben. Kann man aber auch über die> UND- bzw. ODER-Befehle realisieren:>
1
> ; Beispiel für Segment 0
2
> orl P3,#0Fh ;Alle Segment deaktivieren
3
> anl P3,#0Eh ;Segment 0 aktivieren
4
>
also ein Makro ist quasi das gleiche wie ein Unterprogramm, nur es wird
automatisch beendet nachdem das Makro abgearbeitet wurde und der normale
Ablauf weitergeführt?
wird gemacht.
> Weiter gehts...>> - In der ISR fehlt das Stoppen des Timer vor dem Laden
jo wird auch gemacht.
> - Ich habe gepennt! Das ganze läuft ja über Multiplex(stirnklopf), das> heisst, du darfst natürlich nicht nur jede Sekunde updaten, sondern> musst quasi so schnell wie möglich die Ausgabe voranbringen! -> SORRY,> Asche auf mein Haupt.>> Ist aber auch kein Problem, kriegen wir hin ;)> Du musst (leider) nochmal den Timer-Reloadwert und die ISR anpassen.> 50ms entsprechen 20Hz, das ist ein bisschen zu wenig, um als angenehm> empfunden zu werden. 50Hz wären besser, also ein Reloadwert für 20ms.> Die Variable fürs Zählen in der ISR entsprechend abändern auf 50.
ok, wird auch gleich gemacht.
>> Die Variable OUT (wird später ein Bit -> Spart Speicher) solltest du im> Unterprogramm AUSGABE auf ungleich 0 prüfen:>>
1
> mov a,out ;Variable OUT in Akku laden
2
> jz AUSGABE_ENDE ;Wenn Akku 0 ist, Sprung auf Ende
3
> mov out,#0 ;OUT Variable löschen
4
> ...
5
> AUSGABE_ENDE:
6
> ret
7
>
> Diese Variante lässt sich zum einen besser auf die spätere Verwendung> einer Bitvariablen anpassen und zum Anderen kehrt sie ins Hauptprogramm> zurück, wenn OUT gleich 0 ist. Der Vorteil ist momentan für dich> vielleicht nicht klar ersichtlich, er liegt darin, dass du wieder in der> Mainroutine landest, und dort weitere Unterprogramme aufrufen kannst> (beispielsweise Tastaturabfrage), anstatt in der Ausgaberoutine zu> warten (= zu hängen).
jo klingt logisch.
> So, jetzt müsste die Ausgaberoutine genauer unter die Lupe genommen> werden:>> Das von dir erwähnte Flackern, wie genau äussert sich das? Hast du das> Flackern auch, wenn du die Zeitschleife rausnimmst (die du m.E.> überhaupt nicht brauchst!)?> Was bezweckst du mit der Zeitschleife? Willst du damit sicherstellen,> dass die einzelnen Segmente lange genug an sind, um erkannt zu werden?
Mit der Zeitschleife will ich genau das bezwegen, was du geschrieben
hast.
Das Flackern ist so, das man noch erkennt, das die Anzeigen gemultiplext
sind, also nacheinander angeschaltet werden. (Also quasi zu langsam)
>> Soweit ich es im ersten Moment sehen kann, ist die Ausgaberoutine okay.> Was macht dich so sicher, dass die Konvertierung für die Anzeige den> Fehler verursacht?
ich habe das Programm auf den µC übertragen und die 7-Segment-Platine
angeschlossen. Nun zählt er zwar auf der richtigen Zeitbasis aber gibt
an den Segmenten diese Zahlen aus:
bei den Einern:
0
-> falsche Zahl (C spiegelverkehrt?)
-> 2
-> 3
-> falsche Zahl (9?)
-> falsche Zahl (9?)
-> falsche Zahl (8?)
-> falsche Zahl (C spiegelverkehrt?)
-> 8
-> 9
dann fängt er logischerweiße wieder von vorne an
bei den Zehnern:
0
-> falsche Zahl (C spiegelverkehrt?)
-> 2
-> 3
-> falsche Zahl (9?)
-> falsche Zahl (9?)
-> 0
> Die geschickteste Methode, die Ausgabe in Ruhe zu prüfen wäre, den> Timer-Interrupt zu deaktivieren, in der Ausgaberoutine die Variable OUT> zu ignorieren (also Befehl auskommentieren) und einfach im Hauptprogramm> mal die Variablen sec_one, etc. mit festen Werten zu belegen. Und zwar> in zehn Durchläufen für alle Segmente die entsprechenden Ziffern, dann> müsstest du sehen, in welchem Abschnitt was klemmt. Probier das bitte> mal aus.
jo werde ich auch gleich in Angriff nehmen.
Florian
Ausgabe://Ausgabe der Zeit an die 7.Segment-Anzeige
163
164
movA,out//Out in den Akku laden
165
jzAusgabe_Ende//Akku = 0 (keine Freigabe von der ISR) Ausgabe beenden
166
movout,#0//Freigabe von der ISR zurücksetzen
167
168
169
movA,sec_one//Sekunden Einer in den Akku schreiben
170
callSeg_Codierung//die Zahl wird in eine 7-Segment-Zahl umgewandelt
171
movseg7port,A//umgewandelte Zahl wird in P2 geschrieben
172
Seg0_ON//Freigabe der rechten 7.Segment-Anzeige
173
callzeit//Zeitschleife aufrufen
174
175
176
movA,sec_ten//Sekunden Zehner in den Akku schreiben
177
callSeg_Codierung//die Zahl wird in eine 7-Segment-Zahl umgewandelt
178
movseg7port,A//umgewandelte Zahl wird in P2 geschrieben
179
Seg1_ON//Freigabe der mittleren rechten 7.Segment-Anzeige
180
callzeit//Zeitschleife aufrufen
181
182
183
movA,min_one//Minuten Einer in den Akku schreiben
184
callSeg_Codierung//die Zahl wird in eine 7-Segment-Zahl umgewandelt
185
movseg7port,A//umgewandelte Zahl wird in P2 geschrieben
186
Seg2_ON//Freigabe der mittleren linken 7.Segment-Anzeige
187
callzeit//Zeitschleife aufrufen
188
189
190
movA,min_ten//Minuten Zehner in den Akku schreiben
191
callSeg_Codierung//die Zahl wird in eine 7-Segment-Zahl umgewandelt
192
movseg7port,A//umgewandelte Zahl wird in P2 geschrieben
193
Seg3_ON//Freigabe der linken 7.Segment-Anzeige
194
callzeit//Zeitschleife aufrufen
195
196
Ausgabe_Ende:
197
198
ret
199
200
201
Seg_Codierung://wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl
202
203
movdptr,#Tab
204
movcA,@A+dptr
205
ret
206
207
Tab:
208
dbnr0,nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8,nr9
209
210
211
212
Zeit://Zeitschleife
213
214
movA,#0d
215
Zeit1:decA
216
cjneA,#0d,Zeit1
217
movA,#0d
218
Zeit2:decA
219
cjneA,#0d,Zeit2
220
movA,#0d
221
222
ret
223
224
end
leider kann ich im Moment das Programm nicht auf meinen Platinen testen,
da ich dort gerade eben etwas abgeschossen habe (Ausversehen einen
Kurzschluss auf die Platine gegeben)
Hi,
neue Runde :)
> also ein Makro ist quasi das gleiche wie ein Unterprogramm, nur es wird> automatisch beendet nachdem das Makro abgearbeitet wurde und der normale> Ablauf weitergeführt?
Nicht das gleiche wie ein Unterprogramm. Kennst du die Funktion von
#define in C? Es ist quasi dasselbe, überall wo das Schlüsselwort
auftaucht, wird stattdessen der Ersatztext eingegeben. Das heisst, die
Befehle tauchen mehrmals auf. Bei einem Unterprogramm hast du ja einmal
das Unterprogramm selbst, und dann nur noch die CALL-Befehle aufs
Unterprogramm. Beides hat Vor- und Nachteile. Kurzes Beispiel:
1
UNTERPROGRAMM:
2
setb P1.0
3
ret
Das Problem hierbei ist die Platzverschwendung. Der CALL-Befehl und der
RET-Befehl brauchen mehr Platz als die reine Funktion (SETB-Befehl).
Deswegen wäre das direkte Verwenden der Funktion platztechnisch
geschickter als das Unterprogramm. Geschickterweise verpackt man das
dann in ein Makro, damit man nur an einer Stelle ändern muss, wenn sich
die Pinbelegung mal ändern sollte.
> Mit der Zeitschleife will ich genau das bezwegen, was du geschrieben> hast.> Das Flackern ist so, das man noch erkennt, das die Anzeigen gemultiplext> sind, also nacheinander angeschaltet werden. (Also quasi zu langsam)
Hm... Okay, das ist doof. Hört sich an, als ob die Ausgabe zu langsam
fürs Auge ist (< ~35Hz) Bin grad am Überlegen, ob du den gleichen Effekt
bekommst, wenn du beispielsweise auf 200Hz für den Timer gehen würdest
(entsprechend den Reloadwert und die Zählvariable anpassen), und bei
jedem Interrupt jeweils nur eine der Anzeigen bedienst, das ergibt 50Hz
pro Anzeige. Realisieren kannst du das über eine weitere Variable, die
die jeweilige Anzeige (0-3) definiert, die Variable wird bei jedem
Interrupt inkrementiert und bei Zählerstand 4 auf 0 gesetzt. Die
Ausgaberoutine muss prüfen, ob sich der Zustand der Variable seit dem
letzten Durchgang geändert hat, wenn ja, dann ausgeben, wenn nicht,
wieder zurück ins Hauptprogramm. Spätestens ab hier macht die Verwendung
von Bitvariablen Sinn (wie gesagt, das ist Feintuning, kannst du machen,
wenns prinzipiell läuft).
Über den Rest reden wir, wenn du die Reparatur und den statischen Test
durchgeführt hast, okay? :)
Ralf
Ralf schrieb:
> Hi,>> neue Runde :)>>> also ein Makro ist quasi das gleiche wie ein Unterprogramm, nur es wird>> automatisch beendet nachdem das Makro abgearbeitet wurde und der normale>> Ablauf weitergeführt?> Nicht das gleiche wie ein Unterprogramm. Kennst du die Funktion von> #define in C? Es ist quasi dasselbe, überall wo das Schlüsselwort> auftaucht, wird stattdessen der Ersatztext eingegeben. Das heisst, die> Befehle tauchen mehrmals auf. Bei einem Unterprogramm hast du ja einmal> das Unterprogramm selbst, und dann nur noch die CALL-Befehle aufs> Unterprogramm. Beides hat Vor- und Nachteile. Kurzes Beispiel:>>
1
> UNTERPROGRAMM:
2
> setb P1.0
3
> ret
4
>
> Das Problem hierbei ist die Platzverschwendung. Der CALL-Befehl und der> RET-Befehl brauchen mehr Platz als die reine Funktion (SETB-Befehl).> Deswegen wäre das direkte Verwenden der Funktion platztechnisch> geschickter als das Unterprogramm. Geschickterweise verpackt man das> dann in ein Makro, damit man nur an einer Stelle ändern muss, wenn sich> die Pinbelegung mal ändern sollte.
ok, habe ich verstanden ;)
Das mit dem Flackern habe ich in den Griff bekommen, der Timer hat nun
eine Interruptfrequenz von 50 Hz
>> Über den Rest reden wir, wenn du die Reparatur und den statischen Test> durchgeführt hast, okay? :)
den Test werde ich gleich durchführen.
Gruß Florian
Jo, dann gehts Stück für Stück dem Ziel entgegen, hm?
Kann sein, dass wir das eine oder andere doch nochmal über den Haufen
werfen müssen, aber das sehen wir ja dann.
> Das mit dem Flackern habe ich in den Griff bekommen, der Timer hat nun> eine Interruptfrequenz von 50 Hz
Okay, das heisst, alle Segmente werden auf einmal aktualisiert? Wie ist
das dann mit dem letzten Segment der Kette, das müsste ja dann heller
leuchten, weil es am längsten an ist?
Ralf
richtig, das letzte Segment leuchtet heller.
habe gerade ein Testprogramm für die 7Segment Anzeige geschrieben, doch
das läuft auch nicht, es wird nur 8 und 0 dargestellt.
> richtig, das letzte Segment leuchtet heller.
Ich weiss :)
Das liegt daran, dass du alle Segmente "auf einmal" ausgibst, dann
1/50Hz = 20ms wartest, und von vorne loslegst. Deswegen mein Vorschlag
von oben, auf 200Hz zu gehen, und mit jedem Interrupt ein anderes
Segment (und nur eines) anzusteuern. Bei vier Segmenten sind das 200Hz /
4, also wieder 50Hz pro Segment, aber versetzt zu einander.
> habe gerade ein Testprogramm für die 7Segment Anzeige geschrieben, doch> das läuft auch nicht, es wird nur 8 und 0 dargestellt.
Ich weiss :)
Das liegt daran, dass du nicht beachtest, dass der Akku während der
Unterprogramme verändert wird.
Bei einem 8051 ist der Akku das Arbeitsregister (bei einem AVR
beispielsweise sind es eigentlich fast alle Register R0-R31) und sollte
daher bestenfalls als Übergaberegister, keinesfalls aber als
Speicherregister verwendet werden. Die Register R0-R7 sollten ebenfalls
nicht als Speicherregister herhalten, weil auch hier wie beim Akku ein
Großteil der Befehle mit diesen Registern arbeitet, v.a. die 8-Bit
Pointer. Dein Problem kannst du auf zwei Arten beheben. Entweder du
sicherst den Akku in den Unterprogrammen am Anfang auf den Stack (PUSH A
bzw. PUSH ACC) und holst ihn am Ende auch wieder ab(!) (POP A bzw. POP
ACC) oder du spendierst deinem Zähler eine eigene Variable.
Quasi zum Merken:
Jeder Wert, der permanent gebraucht wird, verdient eine eigene Variable.
Wird der Wert wirklich permanent gebraucht, darf an den Speicherplatz
der Variablen natürlich keine andere Variable geschrieben werden.
Damit nun der Speicher nicht ratzfatz voll ist, kann man in Assembler
die Variablen "manuell beurteilen", wann deren Speicherplatz tatsächlich
gebraucht wird, und sie ggf. mit Variablen aus anderen Modulen
überlagern. Diese Überlagerung ist also eine Speicheroptimierung (die
der C-Compiler bzw. der Linker automatisch macht -> effektiver). In
Assembler erreicht man dies durch zwei Speicherbereiche (= Segmente).
Ein Segment gehört einem Modul alleine, das andere Segment ist in allen
Modulen bekannt.
Ralf
Ralf schrieb:
>> richtig, das letzte Segment leuchtet heller.> Ich weiss :)> Das liegt daran, dass du alle Segmente "auf einmal" ausgibst, dann> 1/50Hz = 20ms wartest, und von vorne loslegst. Deswegen mein Vorschlag> von oben, auf 200Hz zu gehen, und mit jedem Interrupt ein anderes> Segment (und nur eines) anzusteuern. Bei vier Segmenten sind das 200Hz /> 4, also wieder 50Hz pro Segment, aber versetzt zu einander.
ok, werde ich gleich mal ausprobieren.
>> habe gerade ein Testprogramm für die 7Segment Anzeige geschrieben, doch>> das läuft auch nicht, es wird nur 8 und 0 dargestellt.> Ich weiss :)> Das liegt daran, dass du nicht beachtest, dass der Akku während der> Unterprogramme verändert wird.>> Bei einem 8051 ist der Akku das Arbeitsregister (bei einem AVR> beispielsweise sind es eigentlich fast alle Register R0-R31) und sollte> daher bestenfalls als Übergaberegister, keinesfalls aber als> Speicherregister verwendet werden. Die Register R0-R7 sollten ebenfalls> nicht als Speicherregister herhalten, weil auch hier wie beim Akku ein> Großteil der Befehle mit diesen Registern arbeitet, v.a. die 8-Bit> Pointer. Dein Problem kannst du auf zwei Arten beheben. Entweder du> sicherst den Akku in den Unterprogrammen am Anfang auf den Stack (PUSH A> bzw. PUSH ACC) und holst ihn am Ende auch wieder ab(!) (POP A bzw. POP> ACC) oder du spendierst deinem Zähler eine eigene Variable.
also dass mit push und pop klappt nicht. Er zählt immer noch wie oben
beschrieben
Was meinst du mit eigener Variabe spendieren?
Ich habe doch R0 und A nur zum Zwischenspeichern, bzw. der movc Befehl
geht nur mit A
> Quasi zum Merken:> Jeder Wert, der permanent gebraucht wird, verdient eine eigene Variable.> Wird der Wert wirklich permanent gebraucht, darf an den Speicherplatz> der Variablen natürlich keine andere Variable geschrieben werden.>> Damit nun der Speicher nicht ratzfatz voll ist, kann man in Assembler> die Variablen "manuell beurteilen", wann deren Speicherplatz tatsächlich> gebraucht wird, und sie ggf. mit Variablen aus anderen Modulen> überlagern. Diese Überlagerung ist also eine Speicheroptimierung (die> der C-Compiler bzw. der Linker automatisch macht -> effektiver). In> Assembler erreicht man dies durch zwei Speicherbereiche (= Segmente).> Ein Segment gehört einem Modul alleine, das andere Segment ist in allen> Modulen bekannt.>> Ralf
> also dass mit push und pop klappt nicht. Er zählt immer noch wie oben> beschrieben
Ich kann leider nicht hellsehen, WO du das jetzt eingesetzt hast :)
Aber das hier sollte helfen, von mir eingefügte/geänderte Zeilen haben
einen Kommentar:
1
mainl1: mov A,#0 ;mainl1-Label eingefügt
2
mainl2: call Ausgabe ;ml in mainl2 umbenannt
3
call Zeit
4
inc A
5
cjne A,#10,mainl2 ;Wenn Akku kleiner als 10, Sprung auf mainl2
6
jmp main11 ;ansonsten Akku löschen
7
8
Ausgabe:
9
push acc ;Akku auf Stack sichern(je nach Assembler PUSH A)
10
mov dptr,#TAB
11
movc A,@A+dptr
12
mov P1,A
13
clr p2.0
14
pop acc ;Akku vom Stack holen(je nach Assembler POP A)
15
ret
WICHTIG: Damit mit dem Stack vernünftig arbeiten kann, muss man den
Stackpointer initialisieren, er ist nach einem Reset auf 0x07
eingestellt. Bei jeder auf den Stack sichernden Position wird der
Stackpointer zuerst inkrementiert, dann gesichert. Beim Holen vom Stack
entsprechend umgekehrt. Die Reseteinstellung von 0x07 bedeutet, dass R0
von Registerbank 1 bzw. die Adresse 0x08 beschrieben wird, wenn du den
Stack also nicht vernünftig initialisierst, kann es passieren, dass die
Variablen früher oder später, in Abhängigkeit der verwendeten PUSH/CALL
Operationen überschrieben werden, da der Stack nach oben wächst! Bei
einem 80x2 setzt man den Stack üblicherweise in der Initialisierung auf
0x7F. Der Stack wird immer indirekt adressiert, den SFRs passiert also
nix :)
Bei einem 80x1 muss man da schon eher aufpassen, weil dort kein indirekt
adressierbarer Speicher parallel zu den SFRs existiert, sondern das
normal verwendbare RAM nur bis 0x7F (= 128 Bytes) vorhanden ist.
1
main:
2
...
3
mov SP,#7Fh ;Stackpointer initialisieren
4
...
5
mainl:
6
...
7
jmp mainl
> Was meinst du mit eigener Variabe spendieren?
Na, dass z.B. ein Zähler nicht im Akku oder einem Register gehalten
wird, sondern einen eigenen Speicherplatz bekommt:
1
...
2
dseg at 0030h ;(oder rseg DATENSEGMENT -> muss vorher eingerichtet werden)
3
...
4
CTR1 ds 1 ;reserviere 1 Byte im RAM für Variable CTR1
> Ich habe doch R0 und A nur zum Zwischenspeichern, bzw. der movc Befehl> geht nur mit A
Ja und? Dann holst du den Inhalt der Variablen in den Akku, mit dem du
wiederum per MOVC die Daten holst. Danach inkrementierst du halt nicht
den Akku, sondern die Variable :)
Wenn nötig, Akku vorher wie bereits erwähnt auf dem Stack sichern.
Ralf
PS: Die EQUs aus deinem letzten Code-Schnipsel bitte vor das ORG 0000h
setzen :) Kannst auch gerne mal den kompletten Code posten, damit ich
wieder auf Stand bin...
das Problem ist wieder, dass er folgendermaßen zählt:
0
-> falsche Zahl (C spiegelverkehrt?)
-> 2
-> 3
-> falsche Zahl (9?)
-> falsche Zahl (9?)
-> falsche Zahl (8?)
-> falsche Zahl (C spiegelverkehrt?)
-> 8
-> 9
fängt wieder von vorne an
Und Du bist Dir sicher, daß Deine Schaltung in ordnung ist?
Mach mal folgendes Testprogramm:
loop:
zeige Segment A an
Delay 1s
zeige Segment B an
Delay 1s
zeige Segment C an
Delay 1s
...
zeige Segment H an
Delay 1s
jmp loop
Peter
Jo, den Test hätte ich jetzt dann auch vorgeschlagen. Entweder deine
Portzuordnung stimmt nicht, oder du hast Kurzschlüsse. Anders kann ich
mir eine nach deiner Beschreibung offenbar identische Anzeige von z.B. 4
und 5 nicht erklären. Denk dran, dass ein Kurzschluss sowohl vor als
auch hinter dem ULN2803 sein kann!
Ralf
omg, die scheiss Platine hat echt einen Macken, 3 Diggits eines
Segmentes leuchten dauerhaft.
A
-
G| H |B
-
F| E |D
-
A, B und E
wenn mann diese 3 Diggits abzieht stimmen die Zahlen oben...
muss jetzt erst mal den Fehler finden...
So, der Fehler war, das 2 Flachbandleitungen einen Fehler hatten.
Mit den neuen Leitungen funktioniert der Zähler super.
Da das letzte Segment heller leuchtet, erde nun den Timer auf 200Hz
erhöhen In der ISR natürlich dann jeweils eine anderes Segment ausgeben
(4*50Hz)
Wenn dies klappt werde ich mich mal um die Start/Stop-Taste und die
Zwischenzeit/Reset-Taste kümmern
Florian K. schrieb:
> So, der Fehler war, das 2 Flachbandleitungen einen Fehler hatten.>> Mit den neuen Leitungen funktioniert der Zähler super.> Da das letzte Segment heller leuchtet, erde nun den Timer auf 200Hz> erhöhen In der ISR natürlich dann jeweils eine anderes Segment ausgeben> (4*50Hz)>> Wenn dies klappt werde ich mich mal um die Start/Stop-Taste und die> Zwischenzeit/Reset-Taste kümmern
Also mit 200Hz klappt die Anzeige auch so schon, muss sie gar nicht mehr
Vierteln. Nun kommen wir zu den Externen Interrupts
> Also mit 200Hz klappt die Anzeige auch so schon, muss sie gar nicht mehr> Vierteln.
Ja, ist subjektiv richtig, aber in einer Profianwendung würd ich pro
Interrupt eine Segmentausgabe machen, einfach aus dem Grund, weil
üblicherweise bei Multiplex der LED-Strom am Maximum liegt und somit die
Lebensdauer der LEDs (schneller) sinkt, was dann wiederum bedeutet, dass
das letzte Segment schneller stirbt als die anderen...
> Nun kommen wir zu den Externen Interrupts
Mit denen du was machen willst? Tasten? Schlechte Idee, weil man bei
einem normalen 8052 zu wenig externe Interrupts hat (es sei denn, dein
Derivat hat z.B. sogenannte Keyboard-Interrupts). Da bietet sich der
umgekehrte Weg zu deiner LED-Steuerung an, also die Matrixverschaltung,
wenn's mehr wie acht Tasten sind.
Übrigens kannst du das auch direkt in deinem Timer-Interrupt lösen (ohne
externe Interrupts). Ich geh mal von nicht mehr als vier Tasten aus,
also keine Matrixtastatur.
Bei jedem Durchlauf (1/200Hz = 5ms) liest du die Tasten ein. Ist eine
Taste gedrückt, erhält eine Variable den Tastenwert, ansonsten den Wert
Null.
Im nächsten Durchlauf prüfst du, ob die gleiche Taste immer noch
gedrückt ist. Wenn ja, inkrementierst du einen Zähler. Hat der Zähler
einen bestimmten Wert erreicht, übergibst du den Variableninhalt an eine
weitere Variable, die du im Hauptprogramm abfragst und zurücksetzt.
Somit erreichst du erstens ein Entprellen (durch den Zähler) und
zweitens verhinderst du das erneute Erkennen der Taste. Unterscheidet
sich die erkannte Taste von der Taste des vorherigen Durchlaufs, setzt
du den Zähler auf Null zurück. Du brauchst dazu noch ein oder zwei
zusätzliche Variablen zum Zwischenspeichern.
Ralf
Also ich stelle mir das so vor:
Durch Druck auf die Start-Taste beginnt die Stoppuhr zu zählen. Die
dabei verstrichene Zeit wird angezeigt.
Durch erneuten Druck auf die Start-Taste stoppt die Stoppuhr und zeigt
die verstrichene Zeit an.
Wird jetzt die Zwischenzeit-Taste gedrückt, wird die Zeit zurückgesetzt.
Wird bei laufender Stoppuhr die Zwischenzeit Taste gedrückt, wird die
bis dahin verstrichene Zeit angezeigt. Die Stoppuhr zählt hingegen im
Hintergrund weiter. Wird die Zwischenzeit-Taste erneut gedrückt, wird
die im Hintergrund gezählte Zeit wieder angezeigt und zählt ganz normal
weiter.
> Also ich stelle mir das so vor:> ...
Ne ne, erst das Lesen der Tasten an sich. Das, was du dann damit
machen willst, kommt hinterher, du bist zu schnell :)
Wie sind deine zwei/drei/viele Tasten angebunden? Pro Taste ein Portpin?
Schaltplan?
Ralf
so, habe das Programm jetzt so gestaltet, das jede Anzeige nach jeweils
100Hz angezeigt wird.
Des Weiteren habe ich eine Fehlermeldung eingebaut, die erscheint,
nachdem die maximale Messzeit (59:59) überschritten wurde.
1
//Florian Krämer
2
//Projekt Stoppuhr
3
4
//Definieren von Konstanten (Hinter dem Namen steckt folgender Wert)
DBnr0A,nr1A,nr2A,nr3A,nr4A,nr5A,nr6A,nr7A,nr8A,nr9A//Zahlen mit Dezimalpunkt (Übergang Sekunden-Minuten)
275
276
Zeit://Zeitschleife
277
278
movA,#0d
279
Zeit1:decA
280
cjneA,#0d,Zeit1
281
movA,#0d
282
283
ret
284
285
end
Mit den Tastern fange ich jetzt an, habe schonmal den Startbefehl
hinbekommen.
Die Stopp Abfrage soll den Timer anhalten bzw. wieder starten.
Die Zwischenzeit Abfrage soll die Ausgabe anhalten bzw. wieder starten.
die Abfragen bekomme ich programmiertechnisch auch hin, ich weiß nur
nicht wo ich sie einbinden soll, da ja eben das Problem besteht, dass
die Taster nicht entprellt sind.
Hier mal ein Testprogramm fürs Entprellen.
Ich habs nicht getestet, aber es müßte funktionieren.
Einfach 2 Tasten und 2 LEDs an Deinen 8051 ranpappen. Wo, das kannst Du
oben bei den Hardwarefestlegungen ändern.
1
;------------------------------ Key debounce example --------------------------
Das Beispiel zeigt auch, wie man das Reload der Timer zyklusgenau macht
(z.B. für Uhrenanwendungen).
Du kannst nicht einfach die Timer auf feste Werte setzen, weil der
Zeitpunkt des Interrupteinsprungs variabel ist (Interruptlatenz), durch
die verschiedene Zeit der Befehle, Interruptsperren und andere
Interrupts.
Aber der Timer zählt ja mit, wie lange es gedauert hat, also muß man nur
den Timer zum Verkürzungswert addieren.
Peter