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


von Florian K. (kraemer90)


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"

von Peter D. (peda)


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

von Florian K (Gast)


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))

von Peter D. (peda)


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:
1
dseg    at 30h
2
sec_one:        ds 1
3
sec_ten:        ds 1
4
min_one:        ds 1
5
min_ten:        ds 1
6
stack:          ds 16
7
8
cseg
9
        mov     sp, #stack-1
10
;
11
; ... main code
12
;
13
inc_timer:
14
        inc     sec_one
15
        mov     a, #10
16
        cjne    a, sec_one, _inc_t1
17
        mov     sec_one, #0
18
        inc     sec_ten
19
        mov     r7, sec_ten
20
        cjne    r7, #6, _inc_t1
21
        mov     sec_ten #0
22
        inc     min_one
23
        cjne    a, min_one, _inc_t1
24
        mov     min_one, #0
25
        inc     min_ten
26
        mov     r7, min_ten
27
        cjne    r7, #6, _inc_t1
28
        mov     min_ten, #0
29
_inc_t1:
30
        ret
31
32
end


Peter

von Florian K (Gast)


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?

von Florian K. (kraemer90)


Lesenswert?

sry 4 Doppelpost, aber jetzt bringt KEIL mir jede Menge Fehlermeldungen

"Operation Invalid in this Segment"
1
org 0000h
2
3
jmp Main    //Freimachen der Interrupt-Einsprungadress
4
5
6
7
8
org 000Bh         //Einsprungadresse Timer0-Interrupt
9
10
jmp ISR
11
12
13
org 0100h
14
15
Main:
16
17
dseg  at  0070h
18
sec_one:  ds 1
19
sec_ten:  ds 1
20
min_one:  ds 1
21
min_ten:  ds 1
22
call init          //Initialisierung der verwendeten Register
23
call timer_cfg      //Timer konfigurieren
24
25
26
ISR:          //Interrupt-Service-Routine Timer0
27
28
mov TL0, #0B0h      //Timer0 vorladen
29
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
30
31
reti
32
33
34
init:        //Initialisierung der verwendeten Register
35
36
mov R0, #0      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
37
mov sec_one, #0      //Segment rechts (Sekunden Einer)
38
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
39
mov min_one, #0      //Segment Mitte links (Minuten Einer)
40
mov min_ten, #0      //Segment links (Minuten Zehner)
41
mov R5, #0
42
43
ret
44
45
timer_cfg:        //Timer konfigurieren
46
47
setb EA          //Interrupts generell freigeben      
48
setb ET0        //Timer0-Interrupt freigeben
49
mov TMOD, #00000001b  //Timer0 --> Betriebsmode 1
50
setb TR0        //Timer0 läuft
51
52
mov TL0, #0B0h      //Timer0 vorladen
53
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
54
55
ret

von Peter D. (peda)


Lesenswert?

Florian K. schrieb:
> "Operation Invalid in this Segment"

Da hatter recht, im dseg kann kein Code stehen.

Du hast das "cseg" vergessen.


Peter

von Florian K. (kraemer90)


Angehängte Dateien:

Lesenswert?

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


1
//Florian Krämer 
2
//Projekt Stoppuhr
3
4
org 0000h
5
jmp Main        //Freimachen der Interrupt-Einsprungadress
6
7
8
org 000Bh         //Einsprungadresse Timer0-Interrupt
9
10
ljmp ISR_Timer0
11
12
org 001Bh        //Einsprungadresse Timer1-Interrupt
13
14
ljmp ISR_Timer1
15
16
17
org 0100h
18
19
Main:
20
      
21
22
call init          //Initialisierung der verwendeten Register
23
call timer_cfg      //Timer konfigurieren
24
25
loop:
26
27
28
call Ausgabe
29
call time        //Unterprogramm zur Zeitberechnung aufrufen
30
31
call loop        //Endlosschleife
32
33
34
ISR_Timer0:        //Interrupt-Service-Routine Timer0
35
36
mov TL0, #0B0h      //Timer0 vorladen
37
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
38
mov R5, #1        //Freigabe zur Zeitberechnung
39
reti
40
41
42
ISR_Timer1:        //Interrupt-Service-Routine Timer1
43
44
mov TL1, #0F0h      //Timer1 vorladen
45
mov TH1, #0D8h      //65536-10000=55536 --> D8F0h (10ms)
46
mov R6, #1
47
48
reti
49
50
init:        //Initialisierung der verwendeten Register
51
52
mov R0, #0      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
53
mov R1, #0      //Segment rechts (Sekunden Einer)
54
mov R2, #0      //Segment Mitte recht (Sekunden Zehner)
55
mov R3, #0      //Segment Mitte links (Minuten Einer)
56
mov R4, #0      //Segment links (Minuten Zehner)
57
mov R5, #0      //"Zählfreigabe" durch den Interrupt
58
mov R6, #0      //Ausgabefreigabe durch den Interrupt
59
mov A, #0
60
mov P2, #0
61
mov P3, #0
62
63
64
ret
65
66
timer_cfg:        //Timer konfigurieren
67
68
setb EA          //Interrupts generell freigeben      
69
setb ET0        //Timer0-Interrupt freigeben
70
setb ET1        //Timer1-Interrupt freigeben
71
mov TMOD, #00010001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
72
setb TR0        //Timer0 läuft
73
setb TR1        //Timer1 läuft
74
75
mov TL0, #0B0h      //Timer0 vorladen
76
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
77
78
mov TL1, #0F0h      //Timer1 vorladen
79
mov TH1, #0D8h      //65536-10000=55536 --> D8F0h (10ms)
80
81
ret
82
83
time:
84
85
cjne R5,#1d,time    //Die Zeit kann nur berechnet werden,
86
mov R5, #0d        //wenn der Timer0-Interrupt ausgelöst wurde
87
88
inc R0          //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
89
cjne R0,#20d,weiter    //fals 20 nicht erreicht wurde, Unterprogramm beenden
90
mov R0,#0d        //nach Überlauf Register wieder 0 setzen
91
92
inc R1          //Segment rechts (Sekunden Einer)
93
cjne R1,#10d,weiter    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
94
            //aus dem Unterprogramm 
95
mov R1,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
96
            //und weiter mit Sekunden Zehner fortfahren.
97
98
inc R2          //Segment Mitte rechts (Sekunden Zehner)
99
cjne R2,#6d, weiter    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
100
            //aus dem Unterprogramm 
101
mov R2,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
102
            //und weiter mit Minuten Einer fortfahren.
103
104
inc R3          //Segment Mitte links (Minuten Eíner)
105
cjne R3,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
106
            //aus dem Unterprogramm
107
mov R3,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
108
            //und weiter mit Minuten Zehner fortfahren.
109
110
inc R4          //Segment links (Minuten Zehner)
111
cjne R4,#6d, weiter    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
112
            //aus dem Unterprogramm
113
mov R4,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
114
//jmp Ueberlauf      //und Fehlermeldung ausgeben, da Übergeloffen.
115
116
weiter:
117
ret            //Sprung aus dem Unterprogramm
118
119
Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige
120
121
cjne R6, #1d, Ausgabe  //Es kann nur ausgegeben werden,
122
mov R6, #0d        //wenn der Timer1-Interrupt ausgelöst wurde
123
124
mov A, R1        //Sekunden Einer in den Akku schreiben
125
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
126
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
127
mov P3, #00001110b    //Freigabe der rechten 7.Segment-Anzeige
128
call zeit        //Zeitschleife aufrufen
129
130
mov A, R2          //Sekunden Zehner in den Akku schreiben
131
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
132
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
133
mov P3, #00001101b    //Freigabe der mittleren rechten 7.Segment-Anzeige
134
call zeit        //Zeitschleife aufrufen
135
136
mov A, R3           //Minuten Einer in den Akku schreiben
137
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
138
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
139
mov P3, #00001011b       //Freigabe der mittleren linken 7.Segment-Anzeige
140
call zeit        //Zeitschleife aufrufen
141
142
mov A, R4           //Minuten Zehner in den Akku schreiben
143
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
144
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
145
mov P3, #00000111b    //Freigabe der linken 7.Segment-Anzeige
146
call zeit          //Zeitschleife aufrufen
147
148
149
ret
150
151
152
Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  
153
  mov dptr, #Tab
154
  movc A, @A+dptr
155
  ret
156
157
Tab:
158
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
159
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b
160
161
162
163
Zeit:          //Zeitschleife
164
165
    mov A, #20d
166
Zeit1:  dec A
167
    cjne A, #00d, Zeit1
168
    mov A, #20d
169
Zeit2:  dec A
170
    cjne A, #00h, Zeit2
171
    mov A, #00d
172
173
    ret 
174
175
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

von Ralf (Gast)


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.
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

von Florian K. (kraemer90)


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ß

von Florian K. (kraemer90)


Angehängte Dateien:

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.

1
//Florian Krämer 
2
//Projekt Stoppuhr
3
4
org 0000h
5
6
jmp Main        //Freimachen der Interrupt-Einsprungadress
7
8
org 000Bh         //Einsprungadresse Timer0-Interrupt
9
10
ljmp ISR_Timer0
11
12
org 001Bh        //Einsprungadresse Timer1-Interrupt
13
14
ljmp ISR_Timer1
15
16
17
org 0100h
18
19
Main:
20
      
21
22
call init          //Initialisierung der verwendeten Register
23
call timer_cfg      //Timer konfigurieren
24
call ram_belegen    //Belegen des Rams mit den Zählvariablen
25
loop:
26
27
call Ausgabe
28
29
call time        //Unterprogramm zur Zeitberechnung aufrufen
30
31
jmp loop        //Endlosschleife
32
33
34
ISR_Timer0:        //Interrupt-Service-Routine Timer0
35
36
mov TL0, #0B0h      //Timer0 vorladen
37
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
38
mov R0, #1        //Freigabe zur Zeitberechnung
39
reti
40
41
42
ISR_Timer1:        //Interrupt-Service-Routine Timer1
43
44
mov TL1, #078h      //Timer1 vorladen
45
mov TH1, #0ECh      //65536-5000=60536 --> EC78h (5ms)
46
mov R2, #1
47
48
reti
49
50
init:          //Initialisierung der verwendeten Register
51
52
mov R0, #0        //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
53
mov R1, #0        //"Zählfreigabe" durch den Interrupt
54
mov R2, #0        //Ausgabefreigabe durch den Interrupt
55
mov sec_one, #0      //Segment rechts (Sekunden Einer)
56
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
57
mov min_one, #0      //Segment Mitte links (Minuten Einer)
58
mov min_ten, #0      //Segment links (Minuten Zehner)
59
mov A, #0
60
mov P2, #0
61
mov P3, #0
62
63
64
ret
65
66
timer_cfg:        //Timer konfigurieren
67
68
setb EA          //Interrupts generell freigeben      
69
setb ET0        //Timer0-Interrupt freigeben
70
setb ET1        //Timer1-Interrupt freigeben
71
mov TMOD, #00010001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
72
setb TR0        //Timer0 läuft
73
setb TR1        //Timer1 läuft
74
75
mov TL0, #0B0h      //Timer0 vorladen
76
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
77
78
mov TL1, #078h      //Timer1 vorladen
79
mov TH1, #0ECh      //65536-5000=60536 --> EC78h (5ms)
80
81
ret
82
83
ram_belegen:
84
85
86
dseg at 0030h
87
88
 
89
sec_one:  ds 1
90
sec_ten:  ds 1
91
min_one:  ds 1
92
min_ten:  ds 1
93
94
cseg
95
96
ret
97
98
99
time:
100
101
cjne R0,#1d,time      //Die Zeit kann nur berechnet werden,
102
mov R0, #0d          //wenn der Timer0-Interrupt ausgelöst wurde
103
104
inc R1            //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
105
cjne R1,#20d,weiter      //fals 20 nicht erreicht wurde, Unterprogramm beenden
106
mov R1,#0d          //nach Überlauf Register wieder 0 setzen
107
108
inc sec_one
109
mov R3, sec_one        //Segment rechts (Sekunden Einer)
110
cjne R3,#10d,weiter      //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
111
              //aus dem Unterprogramm 
112
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
113
              //und weiter mit Sekunden Zehner fortfahren.
114
115
inc sec_ten  
116
mov R3, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
117
cjne R3,#6d, weiter      //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
118
              //aus dem Unterprogramm 
119
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
120
              //und weiter mit Minuten Einer fortfahren.
121
122
inc min_one
123
mov R3, min_one        //Segment Mitte links (Minuten Eíner)
124
cjne R3,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
125
              //aus dem Unterprogramm
126
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
127
              //und weiter mit Minuten Zehner fortfahren.
128
129
inc min_ten
130
mov R3, min_ten        //Segment links (Minuten Zehner)
131
cjne R3,#6d, weiter      //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
132
              //aus dem Unterprogramm
133
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
134
jmp weiter          //und Fehlermeldung ausgeben, da Übergeloffen.
135
136
weiter:
137
ret              //Sprung aus dem Unterprogramm
138
139
Ausgabe:          //Ausgabe der Zeit an die 7.Segment-Anzeige
140
141
cjne R2, #1d, Ausgabe    //Es kann nur ausgegeben werden,
142
mov R2, #0d          //wenn der Timer1-Interrupt ausgelöst wurde
143
144
mov A, sec_one        //Sekunden Einer in den Akku schreiben
145
call Seg_Codierung      //die Zahl wird in eine 7-Segment-Zahl umgewandelt
146
mov P2, A          //umgewandelte Zahl wird in P2 geschrieben
147
mov P3, #00001110b      //Freigabe der rechten 7.Segment-Anzeige
148
call zeit          //Zeitschleife aufrufen
149
150
151
mov A, sec_ten          //Sekunden Zehner in den Akku schreiben
152
call Seg_Codierung      //die Zahl wird in eine 7-Segment-Zahl umgewandelt
153
mov P2, A          //umgewandelte Zahl wird in P2 geschrieben
154
mov P3, #00001101b      //Freigabe der mittleren rechten 7.Segment-Anzeige
155
call zeit          //Zeitschleife aufrufen
156
157
158
mov A, min_one           //Minuten Einer in den Akku schreiben
159
call Seg_Codierung         //die Zahl wird in eine 7-Segment-Zahl umgewandelt
160
mov P2, A             //umgewandelte Zahl wird in P2 geschrieben
161
mov P3, #00001011b         //Freigabe der mittleren linken 7.Segment-Anzeige
162
call zeit          //Zeitschleife aufrufen
163
164
165
mov A, min_ten           //Minuten Zehner in den Akku schreiben
166
call Seg_Codierung         //die Zahl wird in eine 7-Segment-Zahl umgewandelt
167
mov P2, A             //umgewandelte Zahl wird in P2 geschrieben
168
mov P3, #00000111b      //Freigabe der linken 7.Segment-Anzeige
169
call zeit            //Zeitschleife aufrufen
170
171
172
ret
173
174
175
Seg_Codierung:        //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  
176
177
  mov dptr, #Tab
178
  movc A, @A+dptr
179
  ret
180
181
Tab:
182
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
183
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b
184
185
186
187
Zeit:            //Zeitschleife
188
189
    mov A, #0d
190
Zeit1:  dec A
191
    cjne A, #0d, Zeit1
192
    mov A, #0d
193
Zeit2:  dec A
194
    cjne A, #0d, Zeit2
195
    mov A, #0d
196
    mov A, #0d
197
Zeit3:  dec A
198
    cjne A, #0d, Zeit3
199
    mov A, #0d
200
    ret 
201
202
end

im Anhang das gesamte Projekt in Keil µVision3

Florian

von Ralf (Gast)


Lesenswert?

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

von Florian K. (kraemer90)


Angehängte Dateien:

Lesenswert?

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

von Florian K. (kraemer90)


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:
>
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.

von Florian K. (kraemer90)


Lesenswert?

1
//Florian Krämer 
2
//Projekt Stoppuhr
3
4
org 0000h
5
6
jmp Main        //Freimachen der Interrupt-Einsprungadress
7
8
org 000Bh         //Einsprungadresse Timer0-Interrupt
9
10
jmp ISR_Timer0
11
12
Main:
13
dseg at 0030h
14
15
 
16
sec_one:  ds 1
17
sec_ten:  ds 1
18
min_one:  ds 1
19
min_ten:  ds 1
20
21
twenty_ms:  ds 1
22
out:    ds 1
23
24
cseg      
25
26
call init          //Initialisierung der verwendeten Register
27
call timer_cfg      //Timer konfigurieren
28
29
30
loop:
31
32
call ausgabe
33
jmp loop        //Endlosschleife
34
35
36
ISR_Timer0:        //Interrupt-Service-Routine Timer0
37
38
inc twenty_ms
39
mov R0, twenty_ms      //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
40
cjne R0,#20d,weiter      //fals 20 nicht erreicht wurde, ISR beenden
41
mov twenty_ms,#0d      //nach Überlauf Register wieder 0 setzen
42
43
inc sec_one
44
mov R0, sec_one        //Segment rechts (Sekunden Einer)
45
cjne R0,#10d,weiter      //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
46
              //aus der ISR  
47
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
48
              //und weiter mit Sekunden Zehner fortfahren.
49
50
inc sec_ten  
51
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
52
cjne R0,#6d, weiter      //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
53
              //aus der ISR 
54
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
55
              //und weiter mit Minuten Einer fortfahren.
56
57
inc min_one
58
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
59
cjne R0,#10d, weiter    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
60
              //aus der ISR 
61
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
62
              //und weiter mit Minuten Zehner fortfahren.
63
64
inc min_ten
65
mov R0, min_ten        //Segment links (Minuten Zehner)
66
cjne R0,#6d, weiter      //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
67
              //aus der ISR 
68
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
69
jmp weiter          //und Fehlermeldung ausgeben, da Übergeloffen.
70
71
weiter:            //Sprung der ISR 
72
73
mov out, #1      
74
mov TL0, #0B0h        //Timer0 vorladen
75
mov TH0, #3Ch        //65536-50000=15536 --> 3CB0h (50ms)
76
reti             
77
78
79
init:          //Initialisierung der verwendeten Register
80
81
mov R0, #0        //Zwischenspeicher Zeitberechnung
82
mov sec_one, #0      //Segment rechts (Sekunden Einer)
83
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
84
mov min_one, #0      //Segment Mitte links (Minuten Einer)
85
mov min_ten, #0      //Segment links (Minuten Zehner)
86
mov twenty_ms, #0    //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
87
mov out, #0        //Freigabe Ausgabe
88
mov A, #0
89
mov P2, #0        //Datenausgabe 7-Segment-Anzeige
90
mov P3, #0        //Freigabe 7-Segment-Anzeige
91
92
ret
93
94
timer_cfg:        //Timer konfigurieren
95
96
setb EA          //Interrupts generell freigeben      
97
setb ET0        //Timer0-Interrupt freigeben
98
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
99
setb TR0        //Timer0 läuft
100
101
102
mov TL0, #0B0h      //Timer0 vorladen
103
mov TH0, #3Ch      //65536-50000=15536 --> 3CB0h (50ms)
104
105
106
ret
107
108
109
Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige
110
111
mov R0, out
112
cjne R0, #1, Ausgabe
113
mov out, #0
114
115
116
mov A, sec_one      //Sekunden Einer in den Akku schreiben
117
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
118
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
119
mov P3, #00001110b    //Freigabe der rechten 7.Segment-Anzeige
120
call zeit        //Zeitschleife aufrufen
121
122
123
mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
124
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
125
mov P2, A        //umgewandelte Zahl wird in P2 geschrieben
126
mov P3, #00001101b    //Freigabe der mittleren rechten 7.Segment-Anzeige
127
call zeit        //Zeitschleife aufrufen
128
129
130
mov A, min_one      //Minuten Einer in den Akku schreiben
131
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
132
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
133
mov P3, #00001011b       //Freigabe der mittleren linken 7.Segment-Anzeige
134
call zeit        //Zeitschleife aufrufen
135
136
137
mov A, min_ten         //Minuten Zehner in den Akku schreiben
138
call Seg_Codierung       //die Zahl wird in eine 7-Segment-Zahl umgewandelt
139
mov P2, A           //umgewandelte Zahl wird in P2 geschrieben
140
mov P3, #00000111b    //Freigabe der linken 7.Segment-Anzeige
141
call zeit          //Zeitschleife aufrufen
142
143
144
ret
145
146
147
Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  
148
149
  mov dptr, #Tab
150
  movc A, @A+dptr
151
  ret
152
153
Tab:
154
db 00111111b, 00000110b, 01011011b, 01001111b, 01100110b 
155
db 01101101b, 01111101b, 00000111b, 01111111b, 01101111b
156
157
158
159
Zeit:          //Zeitschleife
160
161
    mov A, #0d
162
Zeit1:  dec A
163
    cjne A, #0d, Zeit1
164
    mov A, #0d
165
Zeit2:  dec A
166
    cjne A, #0d, Zeit2
167
    mov A, #0d
168
Zeit3:  dec A
169
    cjne A, #1d, Zeit3
170
    mov A, #0d
171
    ret 
172
173
end

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

von Ralf (Gast)


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? :)
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

von Ralf (Gast)


Lesenswert?

Erste Korrektur:
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
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

von Florian K. (kraemer90)


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.


>
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

von Florian K. (kraemer90)


Lesenswert?

so mal alles umgeschrieben:
1
//Florian Krämer 
2
//Projekt Stoppuhr
3
4
//Definieren von Konstanten (Hinter dem Namen steckt folgender Wert)
5
6
  seg7port   equ  P1      //Port mit 7-Segment-Anzeigen
7
  seg0    equ  P2.0    //Freigabe Sekunden Einer Anzeige    
8
  seg1    equ  P2.1    //Freigabe Sekunden Zehner Anzeige 
9
  seg2    equ  P2.2    //Freigabe Minuten Einer Anzeige
10
  seg3    equ  P2.3    //Freigabe Minuten Zehner Anzeige
11
12
  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
13
  nr1      equ 00000110b        //
14
  nr2      equ  01011011b        //                 A-->0
15
  nr3      equ  01001111b         //         -A        B-->1    -0
16
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
17
  nr5      equ  01101101b        //      -G        D-->3    -6
18
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
19
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
20
  nr8      equ  01111111b        //              G-->6
21
  nr9        equ  01101111b        //              H-->7
22
23
dseg at 0030h      //Speicher reservieren im RAM
24
25
sec_one:  ds 1    //Sekunden Einer
26
sec_ten:  ds 1    //Sekunden Zehner
27
min_one:  ds 1    //Minuten Einer
28
min_ten:  ds 1    //Minuten Zehner
29
30
fifty_ms:  ds 1    //Zähler von 0-50 --> 50Hz Interruptfrequenz (20ms) *50 =1s
31
out:    ds 1    //Freigabe der Ausgabe
32
33
34
cseg
35
36
37
org 0000h
38
39
jmp Main        //Freimachen der Interrupt-Einsprungadress
40
41
org 000Bh         //Einsprungadresse Timer0-Interrupt
42
43
jmp ISR_Timer0
44
45
46
Main:
47
48
Seg0_ON Macro      //Makroaktivierung für Rechtes Segment
49
  setb Seg1      //Segment 1 deaktivieren
50
  setb Seg2         //Segment 2 deaktivieren
51
  setb Seg3      //Segment 3 deaktivieren
52
  clr Seg0      //Segment 0 aktivieren
53
endm
54
55
Seg1_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
56
  setb Seg0      //Segment 0 deaktivieren
57
  setb Seg2         //Segment 2 deaktivieren
58
  setb Seg3      //Segment 3 deaktivieren
59
  clr Seg1      //Segment 1 aktivieren
60
endm
61
62
Seg2_ON Macro      //Makroaktivierung für Mittleres Linkes Segment
63
  setb Seg0      //Segment 0 deaktivieren
64
  setb Seg1         //Segment 1 deaktivieren
65
  setb Seg3      //Segment 3 deaktivieren
66
  clr Seg2      //Segment 2 aktivieren
67
endm
68
69
Seg3_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
70
  setb Seg0      //Segment 0 deaktivieren
71
  setb Seg1         //Segment 1 deaktivieren
72
  setb Seg2      //Segment 2 deaktivieren
73
  clr Seg3      //Segment 3 aktivieren
74
endm
75
76
77
call init          //Initialisierung der verwendeten Register
78
call timer_cfg      //Timer konfigurieren
79
80
81
loop:
82
83
call ausgabe
84
jmp loop        //Endlosschleife
85
86
87
ISR_Timer0:          //Interrupt-Service-Routine Timer0
88
89
clr TR0            //Timer0 deaktivieren während der ISR
90
inc fifty_ms
91
mov R0, fifty_ms      //Zähler von 0-50 --> 50Hz Interruptfrequenz (20ms) *50 =1s
92
cjne R0,#50d,ISR_Ende    //fals 20 nicht erreicht wurde, ISR beenden
93
mov fifty_ms,#0d      //nach Überlauf Register wieder 0 setzen
94
95
inc sec_one
96
mov R0, sec_one        //Segment rechts (Sekunden Einer)
97
cjne R0,#10d,ISR_Ende    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
98
              //aus der ISR  
99
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
100
              //und weiter mit Sekunden Zehner fortfahren.
101
102
inc sec_ten  
103
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
104
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
105
              //aus der ISR  
106
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
107
              //und weiter mit Minuten Einer fortfahren.
108
109
inc min_one
110
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
111
cjne R0,#10d, ISR_Ende    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
112
              //aus der ISR  
113
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
114
              //und weiter mit Minuten Zehner fortfahren.
115
116
inc min_ten
117
mov R0, min_ten        //Segment links (Minuten Zehner)
118
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
119
              //aus der ISR  
120
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
121
jmp ISR_Ende        //und Fehlermeldung ausgeben, da Übergeloffen.
122
123
ISR_Ende:          //Sprung aus der ISR  
124
125
mov out, #1      
126
mov TL0, #0E0h        //Timer0 vorladen
127
mov TH0, #0B1h        //65536-20000=45536 --> B1E0h (20ms)
128
setb TR0          //Timer0 wieder aktivieren
129
reti             
130
131
132
init:          //Initialisierung der verwendeten Register
133
134
mov R0, #0        //Zwischenspeicher
135
mov sec_one, #0      //Segment rechts (Sekunden Einer)
136
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
137
mov min_one, #0      //Segment Mitte links (Minuten Einer)
138
mov min_ten, #0      //Segment links (Minuten Zehner)
139
mov fifty_ms, #0    //Zähler von 0-20 --> 20Hz Interruptfrequenz (50ms) *20 =1s
140
mov out, #0        //Freigabe Ausgabe
141
mov A, #0
142
mov P1, #0        //Datenausgabe 7-Segment-Anzeige
143
mov P2, #0        //Freigabe 7-Segment-Anzeige
144
145
ret
146
147
timer_cfg:        //Timer konfigurieren
148
149
setb EA          //Interrupts generell freigeben      
150
setb ET0        //Timer0-Interrupt freigeben
151
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
152
setb TR0        //Timer0 läuft
153
154
155
mov TL0, #0E0h      //Timer0 vorladen
156
mov TH0, #0B1h      //65536-20000=45536 --> B1E0h (20ms)
157
158
159
ret
160
161
162
Ausgabe:        //Ausgabe der Zeit an die 7.Segment-Anzeige
163
164
mov A, out        //Out in den Akku laden
165
jz Ausgabe_Ende      //Akku = 0 (keine Freigabe von der ISR) Ausgabe beenden
166
mov out, #0        //Freigabe von der ISR zurücksetzen
167
168
169
mov A, sec_one      //Sekunden Einer in den Akku schreiben
170
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
171
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
172
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
173
call zeit        //Zeitschleife aufrufen
174
175
176
mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
177
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
178
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
179
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
180
call zeit        //Zeitschleife aufrufen
181
182
183
mov A, min_one      //Minuten Einer in den Akku schreiben
184
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
185
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
186
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
187
call zeit        //Zeitschleife aufrufen
188
189
190
mov A, min_ten         //Minuten Zehner in den Akku schreiben
191
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
192
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
193
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
194
call zeit        //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
  mov dptr, #Tab
204
  movc A, @A+dptr
205
  ret
206
207
Tab:
208
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9
209
210
211
212
Zeit:          //Zeitschleife
213
214
    mov A, #0d
215
Zeit1:  dec A
216
    cjne A, #0d, Zeit1
217
    mov A, #0d
218
Zeit2:  dec A
219
    cjne A, #0d, Zeit2
220
    mov A, #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)

von Ralf (Gast)


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:
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

von Florian K. (kraemer90)


Lesenswert?

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

von Ralf (Gast)


Lesenswert?

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

Ralf

von Florian K. (kraemer90)


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:
>
>
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

von Ralf (Gast)


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

von Florian K. (kraemer90)


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.
1
org 0000h
2
  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
3
  nr1      equ 00000110b        //
4
  nr2      equ  01011011b        //                 A-->0
5
  nr3      equ  01001111b         //         -A        B-->1    -0
6
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
7
  nr5      equ  01101101b        //      -G        D-->3    -6
8
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
9
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
10
  nr8      equ  01111111b        //              G-->6
11
  nr9        equ  01101111b        //              H-->7
12
13
14
mov A,#0
15
m1:call Ausgabe
16
call Zeit
17
inc A
18
jmp m1
19
20
21
22
23
24
Ausgabe:
25
26
mov dptr,#TAB
27
movc A,@A+dptr
28
mov P1,A
29
clr p2.0
30
31
ret 
32
33
Zeit:
34
mov R0, #30
35
m4:mov R1, #255
36
m3:mov R2, #255
37
38
m2:djnz R2, m2
39
djnz R1, m3
40
djnz r0, m4
41
42
ret
43
44
45
TAB:
46
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9

von Ralf (Gast)


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

von Florian K. (kraemer90)


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

von Ralf (Gast)


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:
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...

von Florian K. (kraemer90)


Lesenswert?

hier der aktuelle code:
1
;7Segment_Test
2
;Florian Krämer
3
4
;"Abkürzungen erstellen"
5
6
  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
7
  nr1      equ 00000110b        //
8
  nr2      equ  01011011b        //                 A-->0
9
  nr3      equ  01001111b         //         -A        B-->1    -0
10
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
11
  nr5      equ  01101101b        //      -G        D-->3    -6
12
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
13
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
14
  nr8      equ  01111111b        //              G-->6
15
  nr9        equ  01101111b        //              H-->7
16
17
18
dseg at 0030h      //Speicher reservieren im RAM
19
20
counter:  ds 1    //Zähler Main
21
ctr1:  ds 1      //Zähler Zeitschleife
22
ctr2:  ds 1      //Zähler Zeitschleife    
23
ctr3:  ds 1      //Zähler Zeitschleife  
24
25
cseg
26
27
org 0000h
28
;Initialisierung
29
mov sp,#7Fh        
30
mov counter, #0      
31
mov ctr1, #0
32
mov ctr2, #0
33
mov ctr3, #0
34
35
36
;Main
37
main1:  mov counter ,#0 ;Counter auf Nullsetzen, nachdem er 9 erreicht hat
38
main2:  call  Ausgabe    ;Ausgabe aufrufen
39
call  Zeit        ;Zeitschleife aufrufen
40
inc  counter      ;erhöht den counter
41
mov A, counter       
42
cjne  A,#10,main2      ;Wenn Akku kleiner als 10, Sprung auf mainl
43
jmp  main1          ;ansonsten Counter löschen
44
45
;Ausgabe
46
Ausgabe:
47
push  acc          ;Akku auf Stack sichern
48
mov  dptr,#TAB      ;dptr mit TAB verknüpfen
49
mov A, counter      ;Wert des Counters in den Akku schreiben
50
movc  A,@A+dptr      
51
mov  P1,A        ;Akku an P1 ausgeben
52
clr  p2.0        ;Rechtes Segment einschalten
53
pop  acc          ;Akku vom Stack holen
54
ret
55
56
;Zeitschleife
57
Zeit:          
58
mov ctr1, #20
59
m4:mov ctr2, #100
60
m3:mov ctr3, #200
61
62
m2:djnz ctr3, m2
63
djnz ctr2, m3
64
djnz ctr1, m4
65
66
ret
67
68
69
TAB:
70
db nr0, nr1, nr2, nr3, nr4, nr5, nr6, nr7, nr8, nr9
71
72
73
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

von Peter D. (peda)


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

von Ralf (Gast)


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

von Florian K. (kraemer90)


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...

von Ralf (Gast)


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

von Florian K. (kraemer90)


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

von Florian K. (kraemer90)


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

von Ralf (Gast)


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

von Florian K. (kraemer90)


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.

von Ralf (Gast)


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

von Florian K. (kraemer90)


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.

von Ralf (Gast)


Lesenswert?

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

Ralf

von Florian K. (kraemer90)


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.
1
//Florian Krämer 
2
//Projekt Stoppuhr
3
4
//Definieren von Konstanten (Hinter dem Namen steckt folgender Wert)
5
6
  seg7port   equ  P1      //Port mit 7-Segment-Anzeigen
7
  seg0    equ  P2.0    //Freigabe Sekunden Einer Anzeige    
8
  seg1    equ  P2.1    //Freigabe Sekunden Zehner Anzeige 
9
  seg2    equ  P2.2    //Freigabe Minuten Einer Anzeige
10
  seg3    equ  P2.3    //Freigabe Minuten Zehner Anzeige
11
12
  nr0      equ  00111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
13
  nr1      equ 00000110b        //
14
  nr2      equ  01011011b        //                 A-->0
15
  nr3      equ  01001111b         //         -A        B-->1    -0
16
  nr4      equ  01100110b        //    |F    |B       C-->2  |5    |1
17
  nr5      equ  01101101b        //      -G        D-->3    -6
18
  nr6      equ  01111101b        //    |E    |C      E-->4  |4    |2
19
  nr7      equ  00000111b        //      -D       .H    F-->5    -3    .7
20
  nr8      equ  01111111b        //              G-->6
21
  nr9        equ  01101111b        //              H-->7
22
23
24
25
  nr0A      equ  10111111b        //  7-Segment-Anzeige  Zuordnung/Wert           
26
  nr1A      equ 10000110b        //
27
  nr2A      equ  11011011b        //                 A-->0
28
  nr3A      equ  11001111b         //         -A        B-->1    -0
29
  nr4A      equ  11100110b        //    |F    |B       C-->2  |5    |1
30
  nr5A      equ  11101101b        //      -G        D-->3    -6
31
  nr6A      equ  11111101b        //    |E    |C      E-->4  |4    |2
32
  nr7A      equ  10000111b        //      -D       .H    F-->5    -3    .7
33
  nr8A      equ  11111111b        //              G-->6
34
  nr9A          equ  11101111b        //              H-->7
35
36
37
38
39
dseg at 0060h      //Speicher reservieren im RAM
40
41
sec_one:  ds 1    //Sekunden Einer
42
sec_ten:  ds 1    //Sekunden Zehner
43
min_one:  ds 1    //Minuten Einer
44
min_ten:  ds 1    //Minuten Zehner
45
46
ctr_ms:    ds 1    //Zähler von 0-200
47
ctr1_ms:  ds 1    //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
48
out_en:    ds 1    //Freigabe der Ausgabe  (Enable)
49
out_s:    ds 1    //Auswahl der Ausgabe (Select)
50
51
52
cseg
53
54
55
org 0000h
56
57
jmp Main        //Freimachen der Interrupt-Einsprungadress
58
59
org 000Bh         //Einsprungadresse Timer0-Interrupt
60
61
jmp ISR_Timer0
62
63
64
Main:
65
66
Seg0_ON Macro      //Makroaktivierung für Rechtes Segment
67
  setb Seg1      //Segment 1 deaktivieren
68
  setb Seg2         //Segment 2 deaktivieren
69
  setb Seg3      //Segment 3 deaktivieren
70
  clr Seg0      //Segment 0 aktivieren
71
endm
72
73
Seg1_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
74
  setb Seg0      //Segment 0 deaktivieren
75
  setb Seg2         //Segment 2 deaktivieren
76
  setb Seg3      //Segment 3 deaktivieren
77
  clr Seg1      //Segment 1 aktivieren
78
endm
79
80
Seg2_ON Macro      //Makroaktivierung für Mittleres Linkes Segment
81
  setb Seg0      //Segment 0 deaktivieren
82
  setb Seg1         //Segment 1 deaktivieren
83
  setb Seg3      //Segment 3 deaktivieren
84
  clr Seg2      //Segment 2 aktivieren
85
endm
86
87
Seg3_ON Macro      //Makroaktivierung für Mittleres Rechtes Segment
88
  setb Seg0      //Segment 0 deaktivieren
89
  setb Seg1         //Segment 1 deaktivieren
90
  setb Seg2      //Segment 2 deaktivieren
91
  clr Seg3      //Segment 3 aktivieren
92
endm
93
94
Start:          //Taster1 Abfrage
95
jnb P0.0,start
96
97
call init          //Initialisierung der verwendeten Register
98
call timer_cfg      //Timer konfigurieren
99
100
101
loop:
102
103
104
call out          //Ausgabe der Zeit an die 7.Segment-Anzeige
105
jmp loop          //Endlosschleife
106
107
108
ISR_Timer0:          //Interrupt-Service-Routine Timer0
109
110
clr TR0            //Timer0 deaktivieren während der ISR
111
inc ctr_ms
112
mov R0, ctr_ms        //Zähler von 0-200
113
cjne R0,#200d,ISR_Ende    //fals 200 nicht erreicht wurde, ISR beenden
114
mov ctr_ms,#0d        //nach Überlauf Register wieder 0 setzen
115
inc ctr1_ms          
116
mov R0, ctr1_ms        //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
117
cjne R0,#2, ISR_Ende    //fals 2 nicht erreicht wurde, ISR beenden
118
mov ctr1_ms, #0        //nach Überlauf Register wieder 0 setzen
119
120
121
inc sec_one
122
mov R0, sec_one        //Segment rechts (Sekunden Einer)
123
cjne R0,#10d,ISR_Ende    //Zählt jede Sekunde 1 hoch. Unter 10 Sprung 
124
              //aus der ISR  
125
mov sec_one,#0d        //Fals 10 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
126
              //und weiter mit Sekunden Zehner fortfahren.
127
128
inc sec_ten  
129
mov R0, sec_ten        //Segment Mitte rechts (Sekunden Zehner)
130
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Sekunden 1 hoch. Unter 6 Sprung 
131
              //aus der ISR  
132
mov sec_ten,#0d        //Fals 60 Sekunden erreicht wurden, wieder auf 0 Rücksetzen
133
              //und weiter mit Minuten Einer fortfahren.
134
135
inc min_one
136
mov R0, min_one        //Segment Mitte links (Minuten Eíner)
137
cjne R0,#10d, ISR_Ende    //Zählt jede 60 Sekunden 1 hoch. Unter 10 Sprung 
138
              //aus der ISR  
139
mov min_one,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen 
140
              //und weiter mit Minuten Zehner fortfahren.
141
142
inc min_ten
143
mov R0, min_ten        //Segment links (Minuten Zehner)
144
cjne R0,#6d, ISR_Ende    //Zählt jede 10 Minuten 1 hoch. Unter 60 Sprung 
145
              //aus der ISR  
146
mov min_ten,#0d        //Fals 10 Minuten erreicht wurden, wieder auf 0 Rücksetzen
147
jmp fail          //Fehlermeldung ausgeben
148
        
149
ISR_Ende:          //Sprung aus der ISR  
150
inc out_s          //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
151
mov out_en, #1        //Freigabe der Ausgabe  (Enable)
152
mov TL0, #03Ch        //Timer0 vorladen
153
mov TH0, #0F6h        //65536-2500=63036 --> F63C (2,5ms) 400Hz
154
setb TR0          //Timer0 wieder aktivieren
155
reti             
156
157
158
init:          //Initialisierung der verwendeten Register
159
160
mov R0, #0        //Zwischenspeicher
161
mov R1, #0        //Zwischenspeicher
162
mov sec_one, #0      //Segment rechts (Sekunden Einer)
163
mov sec_ten, #0      //Segment Mitte recht (Sekunden Zehner)
164
mov min_one, #0      //Segment Mitte links (Minuten Einer)
165
mov min_ten, #0      //Segment links (Minuten Zehner)
166
mov ctr_ms, #0      //Zähler von 0-200
167
mov ctr1_ms, #0      //Zähler von 0-2 --> 2*200 = 400 *(2,5ms) =1s
168
mov out_en, #0      //Freigabe Ausgabe (Enable)
169
mov out_s, #0      //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
170
mov P0, #0        //Tasterabfrage
171
mov P1, #0        //Datenausgabe 7-Segment-Anzeige
172
mov P2, #0        //Freigabe 7-Segment-Anzeige
173
mov A, #0
174
175
ret
176
177
timer_cfg:        //Timer konfigurieren
178
179
setb EA          //Interrupts generell freigeben      
180
setb ET0        //Timer0-Interrupt freigeben
181
mov TMOD, #00000001b  //Betriebsmode festlegen (jeweils Betriebsmode1)
182
setb TR0        //Timer0 läuft
183
184
185
mov TL0, #03Ch      //Timer0 vorladen
186
mov TH0, #0F6h      //65536-2500=63036 --> F63C (2,5ms) 400Hz
187
188
ret
189
190
191
OUT:          //Ausgabe der Zeit an die 7.Segment-Anzeige
192
193
mov A, out_en      //Out in den Akku laden
194
jz Ausgabe_Ende      //Akku = 0 (keine Freigabe von der ISR) Ausgabe beenden
195
mov out_en, #0      //Freigabe von der ISR zurücksetzen
196
mov R1, out_s      //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
197
cjne R1,#1,Out_mr
198
199
200
OUT_r:
201
mov A, sec_one      //Sekunden Einer in den Akku schreiben
202
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
203
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
204
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
205
jmp Ausgabe_Ende
206
207
OUT_mr:
208
cjne R1,#2,OUT_ml    //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
209
mov A, sec_ten        //Sekunden Zehner in den Akku schreiben
210
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
211
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
212
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
213
jmp Ausgabe_Ende
214
215
OUT_ml:
216
cjne R1,#3,OUT_l    //Auswahl der Ausgabe (jede 100Hz eine anderes Segment)
217
mov A, min_one      //Minuten Einer in den Akku schreiben
218
call Seg_Codierung1    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
219
mov seg7port, A     //umgewandelte Zahl wird in P2 geschrieben
220
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
221
jmp Ausgabe_Ende
222
223
OUT_l:
224
mov A, min_ten         //Minuten Zehner in den Akku schreiben
225
call Seg_Codierung    //die Zahl wird in eine 7-Segment-Zahl umgewandelt
226
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
227
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
228
mov out_s, #0      //nachdem alle Segmente ausgewählt wurden wieder auf 0 setzen.
229
jmp Ausgabe_Ende
230
Ausgabe_Ende:      //Rücksprung ins Hauptprogramm
231
232
ret
233
234
Fail:
235
clr TR0          //Zähler anhalten
236
237
mov A, #01111001b     //E
238
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
239
Seg0_ON          //Freigabe der rechten 7.Segment-Anzeige
240
call zeit
241
242
mov A, #01110001b    //F
243
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
244
Seg1_ON          //Freigabe der mittleren rechten 7.Segment-Anzeige
245
call zeit
246
247
mov A, #01110001b    //F
248
mov seg7port, A     //umgewandelte Zahl wird in P2 geschrieben
249
Seg2_ON          //Freigabe der mittleren linken 7.Segment-Anzeige
250
call zeit
251
252
mov A, #01110111b    //A
253
mov seg7port, A      //umgewandelte Zahl wird in P2 geschrieben
254
Seg3_ON          //Freigabe der linken 7.Segment-Anzeige
255
call zeit
256
257
jmp Fail
258
Seg_Codierung:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl  
259
260
  mov dptr, #Tab
261
  movc A, @A+dptr
262
  ret
263
264
Seg_Codierung1:      //wandelt die berechnete Zeit (Zahl) in eine 7-Segment-Zahl mit
265
            //mit Dezimalpunkt
266
  mov dptr, #Tab1
267
  movc A, @A+dptr
268
  ret
269
270
Tab:
271
DB nr0,nr1,nr2,nr3,nr4,nr5,nr6,nr7,nr8,nr9        //normale Zahlen
272
273
Tab1:
274
DB nr0A,nr1A,nr2A,nr3A,nr4A,nr5A,nr6A,nr7A,nr8A,nr9A   //Zahlen mit Dezimalpunkt (Übergang Sekunden-Minuten)
275
276
Zeit:          //Zeitschleife
277
278
    mov A, #0d
279
Zeit1:  dec A
280
    cjne A, #0d, Zeit1
281
    mov A, #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.

von Peter D. (peda)


Angehängte Dateien:

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.
1
;------------------------------  Key debounce example --------------------------
2
3
;------------------------------  Hardware connections --------------------------
4
KEY_INPUT  equ P1      ; key 0, 1 on port P1.0, P1.1
5
KEY0    equ 0
6
KEY1    equ 1
7
LED0    equ P2.0
8
LED1    equ P1.7
9
;------------------------------  Data ------------------------------------------
10
  dseg  at 30h
11
key_state:  ds 1
12
key_press:  ds 1
13
key_ct0:  ds 1
14
key_ct1:  ds 1
15
stack:    ds 16
16
;------------------------------  Code ------------------------------------------
17
  cseg
18
  jmp  init
19
;------------------------------  Interrupt vector table ------------------------
20
  org  0000Bh      ; Interrupt T0
21
  jmp  int_t0
22
;------------------------------  Interrupt handler -----------------------------
23
24
;Timebase 10ms at 12MHz: 12MHz / 12 / 100Hz = 10000
25
T0_reload  equ  10000
26
;
27
int_t0:          ; every 10ms (= 100Hz)
28
  push  psw      ; save registers
29
  push  acc
30
;------------------------------  Reload for exact 10ms time base ---------------
31
  clr  ea      ; no additional delay by other interrupts
32
  clr  tr0      ; no overflow during addition
33
  mov  a, tl0
34
  add  a, #low(8-T0_reload)  ; stop for 8 cycle
35
  mov  tl0, a
36
  mov  a, th0
37
  addc  a, #high(8-T0_reload)
38
  mov  th0, a
39
  setb  ea      ; other interrupts enabled after next instr.
40
  setb  tr0
41
;------------------------------  Key debounce ----------------------------------
42
  mov  a, KEY_INPUT
43
  cpl  a      ; key inverted (low active)
44
  xrl  a, key_state    ; key changed ?
45
46
  anl  key_ct0, a    ; reset or count CT0
47
  xrl  key_ct0, #0FFh
48
49
  anl  a, key_ct1    ; reset or count CT1
50
  xrl  a, key_ct0
51
  mov  key_ct1, a
52
53
  cpl  a      ; if roll over ?
54
  anl  a, key_ct0
55
56
  xrl  key_state, a    ; then toggle debounced state
57
  
58
  anl  a, key_state
59
  orl  key_press, a    ; 0 -> 1: key press detect
60
61
;------------------------------  Insert other interrupt stuff ------------------
62
          ; e.g. display multiplex
63
          ; e.g. count time
64
;------------------------------------------------------------------------------
65
  pop  acc
66
  pop  psw
67
  reti
68
;------------------------------  Subroutines -----------------------------------
69
70
;------------------------------  get key press ---------------------------------
71
;Input: A = key mask
72
;Output: A != 0: key pressed
73
;
74
get_key_press:
75
  clr  EA      ; interrupt disable
76
  anl  a, key_press
77
  xrl  key_press, a
78
  setb  EA
79
  ret
80
;------------------------------  Init stuff-------------------------------------
81
init:
82
  mov  SP, #stack-1    ; set stack after used data
83
  mov  TMOD, #1    ; T0: Mode 1 (16 bit)
84
  setb  TR0      ; run T0
85
  setb  ET0      ; enable T0 interrupt
86
  setb  EA
87
;------------------------------  Main loop -------------------------------------
88
main:
89
  mov  a, #1 shl KEY0    ; bit mask of key 0
90
  call  get_key_press
91
  jz  _main1
92
  CPL  LED0      ; toggle LED 0
93
_main1:
94
  mov  a, #1 shl KEY1    ; bit mask of key 1
95
  call  get_key_press
96
  jz  main
97
  cpl  LED1      ; toggle LED 1
98
  jmp  main
99
;------------------------------------------------------------------------------
100
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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.