www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik 8051 7 Seg. Zähler Multiplex


Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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"

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Florian K (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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))

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:

dseg    at 30h
sec_one:        ds 1
sec_ten:        ds 1
min_one:        ds 1
min_ten:        ds 1
stack:          ds 16

cseg
        mov     sp, #stack-1
;
; ... main code
;
inc_timer:
        inc     sec_one
        mov     a, #10
        cjne    a, sec_one, _inc_t1
        mov     sec_one, #0
        inc     sec_ten
        mov     r7, sec_ten
        cjne    r7, #6, _inc_t1
        mov     sec_ten #0
        inc     min_one
        cjne    a, min_one, _inc_t1
        mov     min_one, #0
        inc     min_ten
        mov     r7, min_ten
        cjne    r7, #6, _inc_t1
        mov     min_ten, #0
_inc_t1:
        ret

end


Peter

Autor: Florian K (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
wenn ich das jetzt richtig verstanden habe, kann ich mit dem Befehl DS, 
automatisch einen Platz im Speicher freihalten?

Quasi neue variable Register erschaffen?

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sry 4 Doppelpost, aber jetzt bringt KEIL mir jede Menge Fehlermeldungen

"Operation Invalid in this Segment"
org 0000h

jmp Main    //Freimachen der Interrupt-Einsprungadress




org 000Bh         //Einsprungadresse Timer0-Interrupt

jmp ISR


org 0100h

Main:

dseg  at  0070h
sec_one:  ds 1
sec_ten:  ds 1
min_one:  ds 1
min_ten:  ds 1
call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren


ISR:          //Interrupt-Service-Routine Timer0

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)

reti


init:        //Initialisierung der verwendeten Register

mov R0, #0      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
mov sec_one, #0      //Segment rechts (Sekunden Einer)
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
mov min_one, #0      //Segment Mitte links (Minuten Einer)
mov min_ten, #0      //Segment links (Minuten Zehner)
mov R5, #0

ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
mov TMOD, #00000001b  //Timer0 --> Betriebsmode 1
setb TR0        //Timer0 läuft

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)

ret

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Florian K. schrieb:
> "Operation Invalid in this Segment"

Da hatter recht, im dseg kann kein Code stehen.

Du hast das "cseg" vergessen.


Peter

Autor: Florian K. (kraemer90)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
So ich habe nochmal von neuem angefangen, habe aber immer noch den 
Fehler, dass die 7 Segment Anzeige falsche Diggits ausgibt.


//Florian Krämer 
//Projekt Stoppuhr

org 0000h
jmp Main        //Freimachen der Interrupt-Einsprungadress


org 000Bh         //Einsprungadresse Timer0-Interrupt

ljmp ISR_Timer0

org 001Bh        //Einsprungadresse Timer1-Interrupt

ljmp ISR_Timer1


org 0100h

Main:
      

call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren

loop:


call Ausgabe
call time        //Unterprogramm zur Zeitberechnung aufrufen

call loop        //Endlosschleife


ISR_Timer0:        //Interrupt-Service-Routine Timer0

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
mov R5, #1        //Freigabe zur Zeitberechnung
reti


ISR_Timer1:        //Interrupt-Service-Routine Timer1

mov TL1, #0F0h      //Timer1 vorladen
mov TH1, #0D8h      //65536-10000=55536 --> D8F0h (10ms)
mov R6, #1

reti

init:        //Initialisierung der verwendeten Register

mov R0, #0      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
mov R1, #0      //Segment rechts (Sekunden Einer)
mov R2, #0      //Segment Mitte recht (Sekunden Zehner)
mov R3, #0      //Segment Mitte links (Minuten Einer)
mov R4, #0      //Segment links (Minuten Zehner)
mov R5, #0      //"Zählfreigabe" durch den Interrupt
mov R6, #0      //Ausgabefreigabe durch den Interrupt
mov A, #0
mov P2, #0
mov P3, #0


ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
setb ET1        //Timer1-Interrupt freigeben
mov TMOD, #00010001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
setb TR0        //Timer0 läuft
setb TR1        //Timer1 läuft

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)

mov TL1, #0F0h      //Timer1 vorladen
mov TH1, #0D8h      //65536-10000=55536 --> D8F0h (10ms)

ret

time:

cjne R5,#1d,time    //Die Zeit kann nur berechnet werden,
mov R5, #0d        //wenn der Timer0-Interrupt ausgelöst wurde

inc R0          //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
cjne R0,#20d,weiter    //fals 20 nicht erreicht wurde, Unterprogramm beenden
mov R0,#0d        //nach Überlauf Register wieder 0 setzen

inc R1          //Segment rechts (Sekunden Einer)
cjne R1,#10d,weiter    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
            //aus dem Unterprogramm 
mov R1,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
            //und weiter mit Sekunden Zehner fortfahren.

inc R2          //Segment Mitte rechts (Sekunden Zehner)
cjne R2,#6d, weiter    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
            //aus dem Unterprogramm 
mov R2,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
            //und weiter mit Minuten Einer fortfahren.

inc R3          //Segment Mitte links (Minuten Eíner)
cjne R3,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
            //aus dem Unterprogramm
mov R3,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
            //und weiter mit Minuten Zehner fortfahren.

inc R4          //Segment links (Minuten Zehner)
cjne R4,#6d, weiter    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
            //aus dem Unterprogramm
mov R4,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
//jmp Ueberlauf      //und Fehlermeldung ausgeben, da Übergeloffen.

weiter:
ret            //Sprung aus dem Unterprogramm

Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige

cjne R6, #1d, Ausgabe  //Es kann nur ausgegeben werden,
mov R6, #0d        //wenn der Timer1-Interrupt ausgelöst wurde

mov A, R1        //Sekunden Einer in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001110b    //Freigabe der rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen

mov A, R2          //Sekunden Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001101b    //Freigabe der mittleren rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen

mov A, R3           //Minuten Einer in den Akku schreiben
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001011b       //Freigabe der mittleren linken 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen

mov A, R4           //Minuten Zehner in den Akku schreiben
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00000111b    //Freigabe der linken 7.Segment-Anzeige
call zeit          //Zeitschleife aufrufen


ret


Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  
  mov dptr, #Tab
  movc A, @A+dptr
  ret

Tab:
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b



Zeit:          //Zeitschleife

    mov A, #20d
Zeit1:  dec A
    cjne A, #00d, Zeit1
    mov A, #20d
Zeit2:  dec A
    cjne A, #00h, Zeit2
    mov A, #00d

    ret 

end

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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.
dseg  at  0030h   //Datensegment nach dem bitadressierbaren Bereich
var1 ds 1         //1-Byte Variable
var2 ds 1         //1-Byte Variable
var3 ds 2         //2-Byte Variable
...

cseg
org 0000h
jmp Main        //Freimachen der Interrupt-Einsprungadress
...

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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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ß

Autor: Florian K. (kraemer90)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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.

//Florian Krämer 
//Projekt Stoppuhr

org 0000h

jmp Main        //Freimachen der Interrupt-Einsprungadress

org 000Bh         //Einsprungadresse Timer0-Interrupt

ljmp ISR_Timer0

org 001Bh        //Einsprungadresse Timer1-Interrupt

ljmp ISR_Timer1


org 0100h

Main:
      

call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren
call ram_belegen    //Belegen des Rams mit den Zählvariablen
loop:

call Ausgabe

call time        //Unterprogramm zur Zeitberechnung aufrufen

jmp loop        //Endlosschleife


ISR_Timer0:        //Interrupt-Service-Routine Timer0

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
mov R0, #1        //Freigabe zur Zeitberechnung
reti


ISR_Timer1:        //Interrupt-Service-Routine Timer1

mov TL1, #078h      //Timer1 vorladen
mov TH1, #0ECh      //65536-5000=60536 --> EC78h (5ms)
mov R2, #1

reti

init:          //Initialisierung der verwendeten Register

mov R0, #0        //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
mov R1, #0        //"Zählfreigabe" durch den Interrupt
mov R2, #0        //Ausgabefreigabe durch den Interrupt
mov sec_one, #0      //Segment rechts (Sekunden Einer)
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
mov min_one, #0      //Segment Mitte links (Minuten Einer)
mov min_ten, #0      //Segment links (Minuten Zehner)
mov A, #0
mov P2, #0
mov P3, #0


ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
setb ET1        //Timer1-Interrupt freigeben
mov TMOD, #00010001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
setb TR0        //Timer0 läuft
setb TR1        //Timer1 läuft

mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)

mov TL1, #078h      //Timer1 vorladen
mov TH1, #0ECh      //65536-5000=60536 --> EC78h (5ms)

ret

ram_belegen:


dseg at 0030h

 
sec_one:  ds 1
sec_ten:  ds 1
min_one:  ds 1
min_ten:  ds 1

cseg

ret


time:

cjne R0,#1d,time      //Die Zeit kann nur berechnet werden,
mov R0, #0d          //wenn der Timer0-Interrupt ausgelöst wurde

inc R1            //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
cjne R1,#20d,weiter      //fals 20 nicht erreicht wurde, Unterprogramm beenden
mov R1,#0d          //nach Überlauf Register wieder 0 setzen

inc sec_one
mov R3, sec_one        //Segment rechts (Sekunden Einer)
cjne R3,#10d,weiter      //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
              //aus dem Unterprogramm 
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Sekunden Zehner fortfahren.

inc sec_ten  
mov R3, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
cjne R3,#6d, weiter      //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
              //aus dem Unterprogramm 
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Minuten Einer fortfahren.

inc min_one
mov R3, min_one        //Segment Mitte links (Minuten Eíner)
cjne R3,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
              //aus dem Unterprogramm
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
              //und weiter mit Minuten Zehner fortfahren.

inc min_ten
mov R3, min_ten        //Segment links (Minuten Zehner)
cjne R3,#6d, weiter      //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
              //aus dem Unterprogramm
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
jmp weiter          //und Fehlermeldung ausgeben, da Übergeloffen.

weiter:
ret              //Sprung aus dem Unterprogramm

Ausgabe:          //Ausgabe der Zeit an die 7.Segment-Anzeige

cjne R2, #1d, Ausgabe    //Es kann nur ausgegeben werden,
mov R2, #0d          //wenn der Timer1-Interrupt ausgelöst wurde

mov A, sec_one        //Sekunden Einer in den Akku schreiben
call Seg_Codierung      //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A          //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001110b      //Freigabe der rechten 7.Segment-Anzeige
call zeit          //Zeitschleife aufrufen


mov A, sec_ten          //Sekunden Zehner in den Akku schreiben
call Seg_Codierung      //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A          //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001101b      //Freigabe der mittleren rechten 7.Segment-Anzeige
call zeit          //Zeitschleife aufrufen


mov A, min_one           //Minuten Einer in den Akku schreiben
call Seg_Codierung         //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A             //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001011b         //Freigabe der mittleren linken 7.Segment-Anzeige
call zeit          //Zeitschleife aufrufen


mov A, min_ten           //Minuten Zehner in den Akku schreiben
call Seg_Codierung         //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A             //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00000111b      //Freigabe der linken 7.Segment-Anzeige
call zeit            //Zeitschleife aufrufen


ret


Seg_Codierung:        //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  

  mov dptr, #Tab
  movc A, @A+dptr
  ret

Tab:
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b



Zeit:            //Zeitschleife

    mov A, #0d
Zeit1:  dec A
    cjne A, #0d, Zeit1
    mov A, #0d
Zeit2:  dec A
    cjne A, #0d, Zeit2
    mov A, #0d
    mov A, #0d
Zeit3:  dec A
    cjne A, #0d, Zeit3
    mov A, #0d
    ret 

end

im Anhang das gesamte Projekt in Keil µVision3

Florian

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So, gugge mol do:

http://www.atmel.com/dyn/resources/prod_documents/...

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:
djnz VAR, T0ISRE   //Variable dekrementieren und abfragen
                   //Ist die Variable ungleich 0, Sprung auf Ende
...

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

Autor: Florian K. (kraemer90)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Im Anhang ist der Schaltplan, den Rest lese ich mir jetzt in Ruhe durch 
und probiere es gleich umzusetzen.

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
>
> djnz VAR, T0ISRE   //Variable dekrementieren und abfragen
>                    //Ist die Variable ungleich 0, Sprung auf Ende
> ...
> 
>
> 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.

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
//Florian Krämer 
//Projekt Stoppuhr

org 0000h

jmp Main        //Freimachen der Interrupt-Einsprungadress

org 000Bh         //Einsprungadresse Timer0-Interrupt

jmp ISR_Timer0

Main:
dseg at 0030h

 
sec_one:  ds 1
sec_ten:  ds 1
min_one:  ds 1
min_ten:  ds 1

twenty_ms:  ds 1
out:    ds 1

cseg      

call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren


loop:

call ausgabe
jmp loop        //Endlosschleife


ISR_Timer0:        //Interrupt-Service-Routine Timer0

inc twenty_ms
mov R0, twenty_ms      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
cjne R0,#20d,weiter      //fals 20 nicht erreicht wurde, ISR beenden
mov twenty_ms,#0d      //nach Überlauf Register wieder 0 setzen

inc sec_one
mov R0, sec_one        //Segment rechts (Sekunden Einer)
cjne R0,#10d,weiter      //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
              //aus der ISR  
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Sekunden Zehner fortfahren.

inc sec_ten  
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
cjne R0,#6d, weiter      //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
              //aus der ISR 
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Minuten Einer fortfahren.

inc min_one
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
cjne R0,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
              //aus der ISR 
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
              //und weiter mit Minuten Zehner fortfahren.

inc min_ten
mov R0, min_ten        //Segment links (Minuten Zehner)
cjne R0,#6d, weiter      //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
              //aus der ISR 
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
jmp weiter          //und Fehlermeldung ausgeben, da Übergeloffen.

weiter:            //Sprung der ISR 

mov out, #1      
mov TL0, #0B0h        //Timer0 vorladen
mov TH0, #3Ch        //65536-50000=15536 --> 3CB0h (50ms)
reti             


init:          //Initialisierung der verwendeten Register

mov R0, #0        //Zwischenspeicher Zeitberechnung
mov sec_one, #0      //Segment rechts (Sekunden Einer)
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
mov min_one, #0      //Segment Mitte links (Minuten Einer)
mov min_ten, #0      //Segment links (Minuten Zehner)
mov twenty_ms, #0    //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
mov out, #0        //Freigabe Ausgabe
mov A, #0
mov P2, #0        //Datenausgabe 7-Segment-Anzeige
mov P3, #0        //Freigabe 7-Segment-Anzeige

ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
setb TR0        //Timer0 läuft


mov TL0, #0B0h      //Timer0 vorladen
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)


ret


Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige

mov R0, out
cjne R0, #1, Ausgabe
mov out, #0


mov A, sec_one      //Sekunden Einer in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001110b    //Freigabe der rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001101b    //Freigabe der mittleren rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, min_one      //Minuten Einer in den Akku schreiben
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00001011b       //Freigabe der mittleren linken 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, min_ten         //Minuten Zehner in den Akku schreiben
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
mov P3, #00000111b    //Freigabe der linken 7.Segment-Anzeige
call zeit          //Zeitschleife aufrufen


ret


Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  

  mov dptr, #Tab
  movc A, @A+dptr
  ret

Tab:
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b



Zeit:          //Zeitschleife

    mov A, #0d
Zeit1:  dec A
    cjne A, #0d, Zeit1
    mov A, #0d
Zeit2:  dec A
    cjne A, #0d, Zeit2
    mov A, #0d
Zeit3:  dec A
    cjne A, #1d, Zeit3
    mov A, #0d
    ret 

end

so nochmal überarbeitet. Das Problem ist immer noch die falschen Zahlen, 
die der Datenpointer ausgibt und das flackern der 7 Segment-Anzeigen

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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? :)
dseg at 0030h
 
sec_one:  ds 1
sec_ten:  ds 1
min_one:  ds 1
min_ten:  ds 1

twenty_ms:  ds 1
out:    ds 1

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:
SEGO_EN  EQU  P3.0  ;Steuerleitung für Anzeige 0
SEG1_EN  EQU  P3.1  ;Steuerleitung für Anzeige 1
SEG2_EN  EQU  P3.2  ;Steuerleitung für Anzeige 2
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):
SEGCHAR0 EQU 00111111b
SEGCHAR1 EQU 00000110b
SEGCHAR2 EQU 01011011b
SEGCHAR3 EQU 01001111b
SEGCHAR4 EQU 01100110b
SEGCHAR5 EQU 01101101b
SEGCHAR6 EQU 01111101b
SEGCHAR7 EQU 00000111b
SEGCHAR8 EQU 01111111b
SEGCHAR9 EQU 01101111b
Ist vielleicht nicht unbedingt nötig, aber evtl. hilfreich.

Und weiter gehts...
SEG0_ON  MACRO    ;Makro für Aktivierung von Anzeige 0
  setb  SEG1  ;Segment 1 deaktivieren
  setb  SEG2  ;Segment 2 deaktivieren
  setb  SEG3  ;Segment 3 deaktivieren
  clr  SEG0  ;Segment 0 aktivieren
  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:
; Beispiel für Segment 0
orl  P3,#0Fh    ;Alle Segment deaktivieren
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:
mov  a,out    ;Variable OUT in Akku laden
jz  AUSGABE_ENDE  ;Wenn Akku 0 ist, Sprung auf Ende
mov  out,#0    ;OUT Variable löschen
...
AUSGABE_ENDE:
  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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erste Korrektur:
SEG0_ON  MACRO  ;Makro für Aktivierung von Anzeige 0
  setb  SEG1  ;Segment 1 deaktivieren
  setb  SEG2  ;Segment 2 deaktivieren
  setb  SEG3  ;Segment 3 deaktivieren
  clr  SEG0  ;Segment 0 aktivieren
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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.


>
> dseg at 0030h
> 
> sec_one:  ds 1
> sec_ten:  ds 1
> min_one:  ds 1
> min_ten:  ds 1
> 
> twenty_ms:  ds 1
> out:    ds 1
> 
> 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.

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:
>
> SEGO_EN  EQU  P3.0  ;Steuerleitung für Anzeige 0
> SEG1_EN  EQU  P3.1  ;Steuerleitung für Anzeige 1
> SEG2_EN  EQU  P3.2  ;Steuerleitung für Anzeige 2
> 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):
>
> SEGCHAR0 EQU 00111111b
> SEGCHAR1 EQU 00000110b
> SEGCHAR2 EQU 01011011b
> SEGCHAR3 EQU 01001111b
> SEGCHAR4 EQU 01100110b
> SEGCHAR5 EQU 01101101b
> SEGCHAR6 EQU 01111101b
> SEGCHAR7 EQU 00000111b
> SEGCHAR8 EQU 01111111b
> SEGCHAR9 EQU 01101111b
> 
> Ist vielleicht nicht unbedingt nötig, aber evtl. hilfreich.


wird gemacht.


> Und weiter gehts...
>
> SEG0_ON  MACRO    ;Makro für Aktivierung von Anzeige 0
>   setb  SEG1  ;Segment 1 deaktivieren
>   setb  SEG2  ;Segment 2 deaktivieren
>   setb  SEG3  ;Segment 3 deaktivieren
>   clr  SEG0  ;Segment 0 aktivieren
>   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:
>
> ; Beispiel für Segment 0
> orl  P3,#0Fh    ;Alle Segment deaktivieren
> anl  P3,#0Eh    ;Segment 0 aktivieren
> 

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:
>
>
> mov  a,out    ;Variable OUT in Akku laden
> jz  AUSGABE_ENDE  ;Wenn Akku 0 ist, Sprung auf Ende
> mov  out,#0    ;OUT Variable löschen
> ...
> AUSGABE_ENDE:
>   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).

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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
so mal alles umgeschrieben:
//Florian Krämer 
//Projekt Stoppuhr

//Definieren von Konstanten (Hinter dem Namen steckt folgender Wert)

  seg7port   equ  P1      //Port mit 7-Segment-Anzeigen
  seg0    equ  P2.0    //Freigabe Sekunden Einer Anzeige    
  seg1    equ  P2.1    //Freigabe Sekunden Zehner Anzeige 
  seg2    equ  P2.2    //Freigabe Minuten Einer Anzeige
  seg3    equ  P2.3    //Freigabe Minuten Zehner Anzeige

  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
  nr1      equ 00000110b        //
  nr2      equ  01011011b        //                 A-->0
  nr3      equ  01001111b         //         -A        B-->1    -0
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
  nr5      equ  01101101b        //      -G        D-->3    -6
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
  nr8      equ  01111111b        //              G-->6
  nr9        equ  01101111b        //              H-->7

dseg at 0030h      //Speicher reservieren im RAM

sec_one:  ds 1    //Sekunden Einer
sec_ten:  ds 1    //Sekunden Zehner
min_one:  ds 1    //Minuten Einer
min_ten:  ds 1    //Minuten Zehner

fifty_ms:  ds 1    //Zähler von 0-50 --> 50Hz Interruptfrequenz (20ms) *50 =1s
out:    ds 1    //Freigabe der Ausgabe


cseg


org 0000h

jmp Main        //Freimachen der Interrupt-Einsprungadress

org 000Bh         //Einsprungadresse Timer0-Interrupt

jmp ISR_Timer0


Main:

Seg0_ON Macro      //Makroaktivierung für Rechtes Segment
  setb Seg1      //Segment 1 deaktivieren
  setb Seg2         //Segment 2 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg0      //Segment 0 aktivieren
endm

Seg1_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg2         //Segment 2 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg1      //Segment 1 aktivieren
endm

Seg2_ON Macro      //Makroaktivierung für Mittleres Linkes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg1         //Segment 1 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg2      //Segment 2 aktivieren
endm

Seg3_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg1         //Segment 1 deaktivieren
  setb Seg2      //Segment 2 deaktivieren
  clr Seg3      //Segment 3 aktivieren
endm


call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren


loop:

call ausgabe
jmp loop        //Endlosschleife


ISR_Timer0:          //Interrupt-Service-Routine Timer0

clr TR0            //Timer0 deaktivieren während der ISR
inc fifty_ms
mov R0, fifty_ms      //Zähler von 0-50 --> 50Hz Interruptfrequenz (20ms) *50 =1s
cjne R0,#50d,ISR_Ende    //fals 20 nicht erreicht wurde, ISR beenden
mov fifty_ms,#0d      //nach Überlauf Register wieder 0 setzen

inc sec_one
mov R0, sec_one        //Segment rechts (Sekunden Einer)
cjne R0,#10d,ISR_Ende    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
              //aus der ISR  
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Sekunden Zehner fortfahren.

inc sec_ten  
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
              //aus der ISR  
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Minuten Einer fortfahren.

inc min_one
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
cjne R0,#10d, ISR_Ende    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
              //aus der ISR  
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
              //und weiter mit Minuten Zehner fortfahren.

inc min_ten
mov R0, min_ten        //Segment links (Minuten Zehner)
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
              //aus der ISR  
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
jmp ISR_Ende        //und Fehlermeldung ausgeben, da Übergeloffen.

ISR_Ende:          //Sprung aus der ISR  

mov out, #1      
mov TL0, #0E0h        //Timer0 vorladen
mov TH0, #0B1h        //65536-20000=45536 --> B1E0h (20ms)
setb TR0          //Timer0 wieder aktivieren
reti             


init:          //Initialisierung der verwendeten Register

mov R0, #0        //Zwischenspeicher
mov sec_one, #0      //Segment rechts (Sekunden Einer)
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
mov min_one, #0      //Segment Mitte links (Minuten Einer)
mov min_ten, #0      //Segment links (Minuten Zehner)
mov fifty_ms, #0    //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
mov out, #0        //Freigabe Ausgabe
mov A, #0
mov P1, #0        //Datenausgabe 7-Segment-Anzeige
mov P2, #0        //Freigabe 7-Segment-Anzeige

ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
setb TR0        //Timer0 läuft


mov TL0, #0E0h      //Timer0 vorladen
mov TH0, #0B1h      //65536-20000=45536 --> B1E0h (20ms)


ret


Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige

mov A, out        //Out in den Akku laden
jz Ausgabe_Ende      //Akku = 0 (keine Freigabe von der ISR) Ausgabe beenden
mov out, #0        //Freigabe von der ISR zurücksetzen


mov A, sec_one      //Sekunden Einer in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, min_one      //Minuten Einer in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen


mov A, min_ten         //Minuten Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
call zeit        //Zeitschleife aufrufen

Ausgabe_Ende:

ret


Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  

  mov dptr, #Tab
  movc A, @A+dptr
  ret

Tab:
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9



Zeit:          //Zeitschleife

    mov A, #0d
Zeit1:  dec A
    cjne A, #0d, Zeit1
    mov A, #0d
Zeit2:  dec A
    cjne A, #0d, Zeit2
    mov A, #0d

    ret 

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)

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
UNTERPROGRAMM:
  setb  P1.0
  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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ok, geht klar, bin heute leider nicht dazu gekommen, die Platine zu 
reparieren :( .

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> ok, geht klar, bin heute leider nicht dazu gekommen, die Platine zu
> reparieren :( .
Eile mit Weile :)

Ralf

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
>
>
> UNTERPROGRAMM:
>   setb  P1.0
>   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.

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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.
org 0000h
  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
  nr1      equ 00000110b        //
  nr2      equ  01011011b        //                 A-->0
  nr3      equ  01001111b         //         -A        B-->1    -0
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
  nr5      equ  01101101b        //      -G        D-->3    -6
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
  nr8      equ  01111111b        //              G-->6
  nr9        equ  01101111b        //              H-->7


mov A,#0
m1:call Ausgabe
call Zeit
inc A
jmp m1





Ausgabe:

mov dptr,#TAB
movc A,@A+dptr
mov P1,A
clr p2.0

ret 

Zeit:
mov R0, #30
m4:mov R1, #255
m3:mov R2, #255

m2:djnz R2, m2
djnz R1, m3
djnz r0, m4

ret


TAB:
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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:
mainl1:  mov  A,#0  ;mainl1-Label eingefügt
mainl2:  call  Ausgabe  ;ml in mainl2 umbenannt
call  Zeit
inc  A
cjne  A,#10,mainl2  ;Wenn Akku kleiner als 10, Sprung auf mainl2
jmp  main11    ;ansonsten Akku löschen

Ausgabe:
push  acc    ;Akku auf Stack sichern(je nach Assembler PUSH A)
mov  dptr,#TAB
movc  A,@A+dptr
mov  P1,A
clr  p2.0
pop  acc    ;Akku vom Stack holen(je nach Assembler POP A)
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.
main:
...
mov  SP,#7Fh    ;Stackpointer initialisieren
...
mainl:
...
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:
...
dseg at 0030h ;(oder rseg DATENSEGMENT -> muss vorher eingerichtet werden)
...
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...

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
hier der aktuelle code:
;7Segment_Test
;Florian Krämer

;"Abkürzungen erstellen"

  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
  nr1      equ 00000110b        //
  nr2      equ  01011011b        //                 A-->0
  nr3      equ  01001111b         //         -A        B-->1    -0
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
  nr5      equ  01101101b        //      -G        D-->3    -6
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
  nr8      equ  01111111b        //              G-->6
  nr9        equ  01101111b        //              H-->7


dseg at 0030h      //Speicher reservieren im RAM

counter:  ds 1    //Zähler Main
ctr1:  ds 1      //Zähler Zeitschleife
ctr2:  ds 1      //Zähler Zeitschleife    
ctr3:  ds 1      //Zähler Zeitschleife  

cseg

org 0000h
;Initialisierung
mov sp,#7Fh        
mov counter, #0      
mov ctr1, #0
mov ctr2, #0
mov ctr3, #0


;Main
main1:  mov counter ,#0 ;Counter auf Nullsetzen, nachdem er 9 erreicht hat
main2:  call  Ausgabe    ;Ausgabe aufrufen
call  Zeit        ;Zeitschleife aufrufen
inc  counter      ;erhöht den counter
mov A, counter       
cjne  A,#10,main2      ;Wenn Akku kleiner als 10, Sprung auf mainl
jmp  main1          ;ansonsten Counter löschen

;Ausgabe
Ausgabe:
push  acc          ;Akku auf Stack sichern
mov  dptr,#TAB      ;dptr mit TAB verknüpfen
mov A, counter      ;Wert des Counters in den Akku schreiben
movc  A,@A+dptr      
mov  P1,A        ;Akku an P1 ausgeben
clr  p2.0        ;Rechtes Segment einschalten
pop  acc          ;Akku vom Stack holen
ret

;Zeitschleife
Zeit:          
mov ctr1, #20
m4:mov ctr2, #100
m3:mov ctr3, #200

m2:djnz ctr3, m2
djnz ctr2, m3
djnz ctr1, m4

ret


TAB:
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9


end

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

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Uärgs, dann sag Bescheid, wenn du den Bug gefunden hast. Bin mal 
gespannt, ob unser Software-Wunderwerk dann gleich auf Anhieb funzt :)

Ralf

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> 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

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Taster gehen auf den Wannenstecker P0.

Ich habe 2 Taster, wie bei einer normalen Stoppuhr.

P0.0
P0.1

der Rest ist unbelegt.

Autor: Ralf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Okay, zwei Tasten geht ja noch. Wie's geht, hab ich ja oben beschrieben. 
Wenn du Fragen dazu hast...

Ralf

Autor: Florian K. (kraemer90)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.
//Florian Krämer 
//Projekt Stoppuhr

//Definieren von Konstanten (Hinter dem Namen steckt folgender Wert)

  seg7port   equ  P1      //Port mit 7-Segment-Anzeigen
  seg0    equ  P2.0    //Freigabe Sekunden Einer Anzeige    
  seg1    equ  P2.1    //Freigabe Sekunden Zehner Anzeige 
  seg2    equ  P2.2    //Freigabe Minuten Einer Anzeige
  seg3    equ  P2.3    //Freigabe Minuten Zehner Anzeige

  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
  nr1      equ 00000110b        //
  nr2      equ  01011011b        //                 A-->0
  nr3      equ  01001111b         //         -A        B-->1    -0
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
  nr5      equ  01101101b        //      -G        D-->3    -6
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
  nr8      equ  01111111b        //              G-->6
  nr9        equ  01101111b        //              H-->7



  nr0A      equ  10111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
  nr1A      equ 10000110b        //
  nr2A      equ  11011011b        //                 A-->0
  nr3A      equ  11001111b         //         -A        B-->1    -0
  nr4A      equ  11100110b        //    |F    |B       C-->2  |5    |1
  nr5A      equ  11101101b        //      -G        D-->3    -6
  nr6A      equ  11111101b        //    |E    |C      E-->4  |4    |2
  nr7A      equ  10000111b        //      -D       .H    F-->5    -3    .7
  nr8A      equ  11111111b        //              G-->6
  nr9A          equ  11101111b        //              H-->7




dseg at 0060h      //Speicher reservieren im RAM

sec_one:  ds 1    //Sekunden Einer
sec_ten:  ds 1    //Sekunden Zehner
min_one:  ds 1    //Minuten Einer
min_ten:  ds 1    //Minuten Zehner

ctr_ms:    ds 1    //Zähler von 0-200
ctr1_ms:  ds 1    //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
out_en:    ds 1    //Freigabe der Ausgabe  (Enable)
out_s:    ds 1    //Auswahl der Ausgabe (Select)


cseg


org 0000h

jmp Main        //Freimachen der Interrupt-Einsprungadress

org 000Bh         //Einsprungadresse Timer0-Interrupt

jmp ISR_Timer0


Main:

Seg0_ON Macro      //Makroaktivierung für Rechtes Segment
  setb Seg1      //Segment 1 deaktivieren
  setb Seg2         //Segment 2 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg0      //Segment 0 aktivieren
endm

Seg1_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg2         //Segment 2 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg1      //Segment 1 aktivieren
endm

Seg2_ON Macro      //Makroaktivierung für Mittleres Linkes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg1         //Segment 1 deaktivieren
  setb Seg3      //Segment 3 deaktivieren
  clr Seg2      //Segment 2 aktivieren
endm

Seg3_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
  setb Seg0      //Segment 0 deaktivieren
  setb Seg1         //Segment 1 deaktivieren
  setb Seg2      //Segment 2 deaktivieren
  clr Seg3      //Segment 3 aktivieren
endm

Start:          //Taster1 Abfrage
jnb P0.0,start

call init          //Initialisierung der verwendeten Register
call timer_cfg      //Timer konfigurieren


loop:


call out          //Ausgabe der Zeit an die 7.Segment-Anzeige
jmp loop          //Endlosschleife


ISR_Timer0:          //Interrupt-Service-Routine Timer0

clr TR0            //Timer0 deaktivieren während der ISR
inc ctr_ms
mov R0, ctr_ms        //Zähler von 0-200
cjne R0,#200d,ISR_Ende    //fals 200 nicht erreicht wurde, ISR beenden
mov ctr_ms,#0d        //nach Überlauf Register wieder 0 setzen
inc ctr1_ms          
mov R0, ctr1_ms        //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
cjne R0,#2, ISR_Ende    //fals 2 nicht erreicht wurde, ISR beenden
mov ctr1_ms, #0        //nach Überlauf Register wieder 0 setzen


inc sec_one
mov R0, sec_one        //Segment rechts (Sekunden Einer)
cjne R0,#10d,ISR_Ende    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
              //aus der ISR  
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Sekunden Zehner fortfahren.

inc sec_ten  
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
              //aus der ISR  
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
              //und weiter mit Minuten Einer fortfahren.

inc min_one
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
cjne R0,#10d, ISR_Ende    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
              //aus der ISR  
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
              //und weiter mit Minuten Zehner fortfahren.

inc min_ten
mov R0, min_ten        //Segment links (Minuten Zehner)
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
              //aus der ISR  
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
jmp fail          //Fehlermeldung ausgeben
        
ISR_Ende:          //Sprung aus der ISR  
inc out_s          //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
mov out_en, #1        //Freigabe der Ausgabe  (Enable)
mov TL0, #03Ch        //Timer0 vorladen
mov TH0, #0F6h        //65536-2500=63036 --> F63C (2,5ms) 400Hz
setb TR0          //Timer0 wieder aktivieren
reti             


init:          //Initialisierung der verwendeten Register

mov R0, #0        //Zwischenspeicher
mov R1, #0        //Zwischenspeicher
mov sec_one, #0      //Segment rechts (Sekunden Einer)
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
mov min_one, #0      //Segment Mitte links (Minuten Einer)
mov min_ten, #0      //Segment links (Minuten Zehner)
mov ctr_ms, #0      //Zähler von 0-200
mov ctr1_ms, #0      //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
mov out_en, #0      //Freigabe Ausgabe (Enable)
mov out_s, #0      //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
mov P0, #0        //Tasterabfrage
mov P1, #0        //Datenausgabe 7-Segment-Anzeige
mov P2, #0        //Freigabe 7-Segment-Anzeige
mov A, #0

ret

timer_cfg:        //Timer konfigurieren

setb EA          //Interrupts generell freigeben      
setb ET0        //Timer0-Interrupt freigeben
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
setb TR0        //Timer0 läuft


mov TL0, #03Ch      //Timer0 vorladen
mov TH0, #0F6h      //65536-2500=63036 --> F63C (2,5ms) 400Hz

ret


OUT:          //Ausgabe der Zeit an die 7.Segment-Anzeige

mov A, out_en      //Out in den Akku laden
jz Ausgabe_Ende      //Akku = 0 (keine Freigabe von der ISR) Ausgabe beenden
mov out_en, #0      //Freigabe von der ISR zurücksetzen
mov R1, out_s      //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
cjne R1,#1,Out_mr


OUT_r:
mov A, sec_one      //Sekunden Einer in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
jmp Ausgabe_Ende

OUT_mr:
cjne R1,#2,OUT_ml    //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
jmp Ausgabe_Ende

OUT_ml:
cjne R1,#3,OUT_l    //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
mov A, min_one      //Minuten Einer in den Akku schreiben
call Seg_Codierung1    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A     //umgewandelte Zahl wird in P2 geschrieben
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
jmp Ausgabe_Ende

OUT_l:
mov A, min_ten         //Minuten Zehner in den Akku schreiben
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
mov out_s, #0      //nachdem alle Segmente ausgewählt wurden wieder auf 0 setzen.
jmp Ausgabe_Ende
Ausgabe_Ende:      //Rücksprung ins Hauptprogramm

ret

Fail:
clr TR0          //Zähler anhalten

mov A, #01111001b     //E
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
call zeit

mov A, #01110001b    //F
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
call zeit

mov A, #01110001b    //F
mov seg7port, A     //umgewandelte Zahl wird in P2 geschrieben
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
call zeit

mov A, #01110111b    //A
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
call zeit

jmp Fail
Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  

  mov dptr, #Tab
  movc A, @A+dptr
  ret

Seg_Codierung1:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl mit
            //mit Dezimalpunkt
  mov dptr, #Tab1
  movc A, @A+dptr
  ret

Tab:
DB nr0,nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8,nr9        //normale Zahlen

Tab1:
DB nr0A,nr1A,nr2A,nr3A,nr4A,nr5A,nr6A,nr7A,nr8A,nr9A   //Zahlen mit Dezimalpunkt (Übergang Sekunden-Minuten)

Zeit:          //Zeitschleife

    mov A, #0d
Zeit1:  dec A
    cjne A, #0d, Zeit1
    mov A, #0d

    ret 

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.

Autor: Peter Dannegger (peda)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
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.

;------------------------------  Key debounce example --------------------------

;------------------------------  Hardware connections --------------------------
KEY_INPUT  equ P1      ; key 0, 1 on port P1.0, P1.1
KEY0    equ 0
KEY1    equ 1
LED0    equ P2.0
LED1    equ P1.7
;------------------------------  Data ------------------------------------------
  dseg  at 30h
key_state:  ds 1
key_press:  ds 1
key_ct0:  ds 1
key_ct1:  ds 1
stack:    ds 16
;------------------------------  Code ------------------------------------------
  cseg
  jmp  init
;------------------------------  Interrupt vector table ------------------------
  org  0000Bh      ; Interrupt T0
  jmp  int_t0
;------------------------------  Interrupt handler -----------------------------

;Timebase 10ms at 12MHz: 12MHz / 12 / 100Hz = 10000
T0_reload  equ  10000
;
int_t0:          ; every 10ms (= 100Hz)
  push  psw      ; save registers
  push  acc
;------------------------------  Reload for exact 10ms time base ---------------
  clr  ea      ; no additional delay by other interrupts
  clr  tr0      ; no overflow during addition
  mov  a, tl0
  add  a, #low(8-T0_reload)  ; stop for 8 cycle
  mov  tl0, a
  mov  a, th0
  addc  a, #high(8-T0_reload)
  mov  th0, a
  setb  ea      ; other interrupts enabled after next instr.
  setb  tr0
;------------------------------  Key debounce ----------------------------------
  mov  a, KEY_INPUT
  cpl  a      ; key inverted (low active)
  xrl  a, key_state    ; key changed ?

  anl  key_ct0, a    ; reset or count CT0
  xrl  key_ct0, #0FFh

  anl  a, key_ct1    ; reset or count CT1
  xrl  a, key_ct0
  mov  key_ct1, a

  cpl  a      ; if roll over ?
  anl  a, key_ct0

  xrl  key_state, a    ; then toggle debounced state
  
  anl  a, key_state
  orl  key_press, a    ; 0 -> 1: key press detect

;------------------------------  Insert other interrupt stuff ------------------
          ; e.g. display multiplex
          ; e.g. count time
;------------------------------------------------------------------------------
  pop  acc
  pop  psw
  reti
;------------------------------  Subroutines -----------------------------------

;------------------------------  get key press ---------------------------------
;Input: A = key mask
;Output: A != 0: key pressed
;
get_key_press:
  clr  EA      ; interrupt disable
  anl  a, key_press
  xrl  key_press, a
  setb  EA
  ret
;------------------------------  Init stuff-------------------------------------
init:
  mov  SP, #stack-1    ; set stack after used data
  mov  TMOD, #1    ; T0: Mode 1 (16 bit)
  setb  TR0      ; run T0
  setb  ET0      ; enable T0 interrupt
  setb  EA
;------------------------------  Main loop -------------------------------------
main:
  mov  a, #1 shl KEY0    ; bit mask of key 0
  call  get_key_press
  jz  _main1
  CPL  LED0      ; toggle LED 0
_main1:
  mov  a, #1 shl KEY1    ; bit mask of key 1
  call  get_key_press
  jz  main
  cpl  LED1      ; toggle LED 1
  jmp  main
;------------------------------------------------------------------------------
end


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

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.