Forum: Mikrocontroller und Digitale Elektronik Probleme bei Programmierung von Lichtschranken-Stoppuhr


von Christian (Gast)


Lesenswert?

Hallo Elektroniker,

ich bin gerade dabei für meine Facharbeit eine Stoppuhr mit zwei 
Lichtschranken zu bauen.
Die Hardware habe ich soweit fertig, jetzt fehlt nur noch die 
Programmierung. Nur hier komme einfach nicht weiter.

Mein ATmega8 hat einen 8MHz Quarz und soll eine Stoppuhr mit 5 "7- 
Segmentanzeigen" (Minuten ,Sekunden/Sekunden ,Hundertstel/Hundertstel) 
steuern. Gestartet wird durch eine Lichtschranke und gestoppt durch eine 
andere.
Die Interrupts, Timer für Stoppuhr und Timer für Multiplexing habe ich 
soweit fertig programmiert und mich dabei an den Tutorials auf diesen 
Seiten orientiert.
Bei Timer1 habe ich im CTC-Modus 8000 als Vergleichswert, somit komme 
ich bei 8MHz alle Tausendstel-Sekunde auf einen Interrupt und zähle 
diesen bis 10 hoch um die Hunderdstel zu bekommen.
Bei der Ausgabe komme ich aber nicht weiter.

Das Problem ist das der Wert für zwei LED-Segmente dabei in einem 
Register steht.
Beispielsweise steht im Register R23(Sekunden) = 34. Wie kann ich diesen 
Wert nun auf zwei Elementen ausgeben, sodass vorne die 3(Zehnerstelle) 
und hinten die 4(Einerstelle) steht.
Für das bessere Verständnis habe ich meinen bisherigen Quellcode 
angefügt.
Es wäre nett wenn sich einer mal die Zeit nehmen würde, sich das ganze 
durchlesen und mir vielleicht helfen könnte.

Im Voraus schoneinmal vielen Dank für die Mühe

Beste Grüße
Christian


-Anhang:
   -Quellcode:

.include "m8def.inc"

.def zero  = r1
.def temp  = r16
.def temp1 = r17
.def temp2 = r18
.def temp3 = r19
.def Flag  = r20

.def SubCount = r21  ;8000/80000000(8MHz)=0,001=Tausendstel
                     ; ->10Tausendstel = 1 Hunderstel
.def Hundert  = r22
.def Sekunden = r23
.def Minuten  = r24
.def test     = r25



.org 0x000
         rjmp main            ; Reset Handler
.org INT0addr
         rjmp int0_handler    ; Ext. Interrupt0 Handler
.org INT1addr
         rjmp int1_handler    ; Ext. Interrupt1 Handler
.org OC1Aaddr
           rjmp timer1_compare ;Timer1 Handler
.org OVF0addr
           rjmp    multiplex   ;Multiplexfunktion Handler



main:                           ; Hauptprogramm

        ldi temp, LOW(RAMEND)
        out SPL, temp
        ldi temp, HIGH(RAMEND)  ;Stackpointer aktivieren
        out SPH, temp

        ldi temp, 0b11110011    ;Port D ist (bis auf PD2 und PD3) 
Ausgang
                                ;Anzeige belegt PD0, PD1, PD4, PD5,PD6,
                                ;PD7, PB1, PB2
        out DDRD, temp

        ldi temp, 0b00000110    ;PB1 und PB2 sind Ausgang
        out DDRB, temp

        ldi temp, 0xFF          ;Port C ist Ausgang
        out DDRC, temp


        ldi temp, (1<<ISC01) | (1<<ISC11) ; INT0 und INT1 auf fallende
                                           ;Flanke konfigurieren
        out MCUCR, temp

        ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
        out GICR, temp

                                   ;  initialisieren der Steuerung für 
die
                                   ; Interrupt Routine
        ldi     temp, 0b11111110
        sts     NextDigit, temp

        ldi     temp, 0
        sts     NextSegment, temp

        ldi     temp, ( 1 << CS01 ) | ( 1 << CS00 )
        out     TCCR0, temp

        ldi     temp, 1 << TOIE0
        out     TIMSK, temp


        clr     Hundert             ; Die Uhr auf 0 setzen
        clr     Sekunden
        clr     Minuten
        clr     SubCount
        clr     Flag                ; Flag löschen

        sei                   ; Interrupts aktivieren

loop:
        cpi     flag,0
        breq    loop                ; Warten bis Flag gesetzt wird um
                                     ;Ausgabe zu machen
        ldi     flag,0              ; Flag löschen
;
;
;
;
;
;
;
;
;
;
;       Platz für die Ausgabe(Weiß noch nicht wie ich diese machen 
soll...)
;
;
;
;
;
;
;


multiplex:
           push    temp                ; Register sichern
           push    temp1
           in      temp, SREG
           push    temp
           push    ZL
           push    ZH

           ldi     temp1, 0            ; Alle Segmente ausschalten
           out     PORTC, temp1

                                       ; Das Muster für die nächste 
Stelle
                                        ;ausgeben
                                       ; Dazu zunächst mal berechnen,
                                        ;welches Segment als
                                       ; nächstest ausgegeben werden 
muss
           ldi     ZL, LOW( Segment0 )
           ldi     ZH, HIGH( Segment0 )
           lds     temp, NextSegment
           add     ZL, temp
           adc     ZH, temp1

           ld      temp, Z             ; das entsprechende Muster holen
                                        ;und ausgeben
           out     PORTD, temp

           lds     temp1, NextDigit    ; Und die betreffende Stelle
                                        ;einschalten
           out     PORTC, temp1

           lds     temp, NextSegment
           inc     temp
           sec
           rol     temp1               ; beim nächsten Interrupt kommt
                                        ;reihum die
           cpi     temp1, 0b11101111   ; nächste Stelle dran.
           brne    multi1
           ldi     temp, 0
           ldi     temp1, 0b11111110

multi1:
           sts     NextSegment, temp
           sts     NextDigit, temp1

           pop     ZH                  ; die gesicherten Register
                                        ;wiederherstellen
           pop     ZL
           pop     temp
           out     SREG, temp
           pop     temp1
           pop     temp
           reti



int0_handler:
         push temp             ; Das SREG in temp sichern.
         in   temp, SREG

                                    ; Vergleichswert
        ldi     temp1, high( 8000 - 1 )
        out     OCR1AH, temp1
        ldi     temp1, low( 8000 - 1 )
        out     OCR1AL, temp1
                                    ; CTC Modus einschalten
                                    ; Vorteiler auf 1
        ldi     temp1, ( 1 << WGM12 ) | ( 1 << CS10 )
        out     TCCR1B, temp1

        ldi     temp1, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer 
Compare
        out     TIMSK, temp1


         out SREG, temp        ; Die Register SREG und temp wieder
         pop temp              ; herstellen
         reti

int1_handler:
         push temp             ; Das SREG in temp sichern.
         in   temp, SREG

         ldi     temp1, ( 0 << CS12 ) | ( 0 << CS11 ) | ( 0 << CS10 )
                                               ;Timer anhalten
         out     TCCR1B, temp1

         out SREG, temp        ; Die Register SREG und temp wieder
         pop temp              ; herstellen
         reti


timer1_compare:

      push    temp1               ; temp 1 sichern
        in      temp1,sreg          ; SREG sichern

        inc     SubCount            ; Wenn dies nicht der 10. Interrupt
        cpi     SubCount, 10        ; ist, dann passiert gar nichts
        brne    end_isr


                                ; Überlauf von SubCount
        clr     SubCount            ; SubCount rücksetzen
        inc     Hundert             ; plus 1 Hundertstel
        cpi     Hundert, 100        ; sind 60 Sekunden vergangen?
        brne    Ausgabe             ; wenn nicht kann die Ausgabe schon
                                    ; gemacht werden


                                    ; Überlauf von Hundertstel
        clr     Hundert             ; Hundertstel wieder auf 0 und dafür
        inc     Sekunden            ; plus 1 Sekunde
        cpi     Sekunden, 60        ; sind 60 Minuten vergangen ?
        brne    Ausgabe             ; wenn nicht, -> Ausgabe


                                    ; Überlauf von Sekunden
        clr     Sekunden             ; Sekunden zurücksetzen und dafür
        inc     Minuten             ; plus 1 Minute
        cpi     Minuten, 9          ; nach 9 Minuten, die Minutenanzeige
        brne    Ausgabe             ; wieder zurücksetzen -> Minuten nur
                                     ;ein Segment, größte Zahl ist 9

                                    ; Überlauf von Minuten
        clr     Minuten             ; Minuten rücksetzen



end_isr:

        out     sreg,temp1          ; sreg wieder herstellen
        pop     temp1
        reti                        ; das wars. Interrupt ist fertig



Ausgabe:

        ldi     flag,1              ; Flag setzen, updaten




Codes:
    .db  0b11000000, 0b11111001     ; 0: a, b, c, d, e, f
                                    ; 1: b, c
    .db  0b10100100, 0b10110000     ; 2: a, b, d, e, g
                                    ; 3: a, b, c, d, g
    .db  0b10011001, 0b10010010     ; 4: b, c, f, g
                                    ; 5: a, c, d, f, g
    .db  0b10000010, 0b11111000     ; 6: a, c, d, e, f, g
                                    ; 7: a, b, c
    .db  0b10000000, 0b10010000     ; 8: a, b, c, d, e, f, g
                                    ; 9: a, b, c, d, f, g

.DSEG
NextDigit:   .byte 1         ; Bitmuster für die Aktivierung des 
nächsten Segments
NextSegment: .byte 1         ; Nummer des nächsten aktiven Segments
Segment0:    .byte 1         ; Ausgabemuster für Segment 0
Segment1:    .byte 1         ; Ausgabemuster für Segment 1
Segment2:    .byte 1         ; Ausgabemuster für Segment 2
Segment3:    .byte 1         ; Ausgabemuster für Segment 3
Segment4:    .byte 1         ; Ausgabemuster für Segment 4

von Christian (Gast)


Lesenswert?

Der Code nochmal in einer besseren Version.
1
.include "m8def.inc"
2
3
.def zero  = r1 
4
.def temp  = r16
5
.def temp1 = r17
6
.def temp2 = r18
7
.def temp3 = r19
8
.def Flag  = r20
9
 
10
.def SubCount = r21            ;8000/80000000(8MHz) = 0,001 = Tausendstel -> 10 Tausendstel = 1 Hunderstel
11
.def Hundert  = r22
12
.def Sekunden = r23
13
.def Minuten  = r24
14
.def test     = r25
15
16
17
 
18
.org 0x000
19
         rjmp main            ; Reset Handler
20
.org INT0addr
21
         rjmp int0_handler    ; Ext. Interrupt0 Handler
22
.org INT1addr
23
         rjmp int1_handler    ; Ext. Interrupt1 Handler
24
.org OC1Aaddr
25
           rjmp timer1_compare ;Timer1 Handler
26
.org OVF0addr
27
           rjmp    multiplex   ;Multiplexfunktion Handler
28
29
 
30
 
31
main:                               ; Hauptprogramm
32
 
33
        ldi temp, LOW(RAMEND)
34
        out SPL, temp
35
        ldi temp, HIGH(RAMEND)      ;Stackpointer aktivieren
36
        out SPH, temp
37
 
38
        ldi temp, 0b11110011        ;Port D bis auf PD2 und PD3 ist Ausgang 
39
                                ;Anzeige belegt PD0, PD1, PD4, PD5, PD6, PD7, PB1, PB2
40
        out DDRD, temp
41
 
42
        ldi temp, 0b00000110       ;PB1 und PB2 sind Ausgang
43
        out DDRB, temp
44
45
        ldi temp, 0xFF            ;Port C ist Ausgang
46
        out DDRC, temp
47
48
49
        ldi temp, (1<<ISC01) | (1<<ISC11) ; INT0 und INT1 auf fallende Flanke konfigurieren   
50
        out MCUCR, temp
51
52
        ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
53
        out GICR, temp
54
55
;                                   initialisieren der Steuerung für die
56
;                                     Interrupt Routine
57
        ldi     temp, 0b11111110
58
        sts     NextDigit, temp
59
 
60
        ldi     temp, 0
61
        sts     NextSegment, temp
62
 
63
        ldi     temp, ( 1 << CS01 ) | ( 1 << CS00 )
64
        out     TCCR0, temp
65
 
66
        ldi     temp, 1 << TOIE0
67
        out     TIMSK, temp
68
                
69
 
70
        clr     Hundert             ; Die Uhr auf 0 setzen
71
        clr     Sekunden
72
        clr     Minuten
73
        clr     SubCount
74
        clr     Flag                ; Flag löschen
75
76
        sei                   ; Interrupts allgemein aktivieren
77
 
78
loop:            
79
        cpi     flag,0
80
        breq    loop                ; Warten bis Flag gesetzt wird um Ausgabe zu machen
81
        ldi     flag,0              ; Flag löschen
82
;
83
;
84
;
85
;
86
;
87
;
88
;
89
;
90
;
91
;
92
;       Platz für die Ausgabe(Weiß noch nicht wie ich diese machen soll...)
93
;
94
;
95
;
96
;
97
;
98
;
99
;
100
101
102
multiplex:
103
           push    temp                ; Register sichern
104
           push    temp1
105
           in      temp, SREG
106
           push    temp
107
           push    ZL
108
           push    ZH
109
 
110
           ldi     temp1, 0            ; Alle Segmente ausschalten
111
           out     PORTC, temp1
112
113
                                       ; Das Muster für die nächste Stelle ausgeben
114
                                       ; Dazu zunächst mal berechnen, welches Segment als
115
                                       ; nächstest ausgegeben werden muss
116
           ldi     ZL, LOW( Segment0 ) 
117
           ldi     ZH, HIGH( Segment0 )
118
           lds     temp, NextSegment
119
           add     ZL, temp
120
           adc     ZH, temp1
121
 
122
           ld      temp, Z             ; das entsprechende Muster holen und ausgeben
123
           out     PORTD, temp
124
 
125
           lds     temp1, NextDigit    ; Und die betreffende Stelle einschalten
126
           out     PORTC, temp1
127
 
128
           lds     temp, NextSegment
129
           inc     temp
130
           sec
131
           rol     temp1               ; beim nächsten Interrupt kommt reihum die
132
           cpi     temp1, 0b11101111   ; nächste Stelle dran.
133
           brne    multi1
134
           ldi     temp, 0
135
           ldi     temp1, 0b11111110
136
 
137
multi1:
138
           sts     NextSegment, temp
139
           sts     NextDigit, temp1
140
 
141
           pop     ZH                  ; die gesicherten Register wiederherstellen
142
           pop     ZL
143
           pop     temp
144
           out     SREG, temp
145
           pop     temp1
146
           pop     temp
147
           reti
148
149
150
 
151
int0_handler:
152
         push temp             ; Das SREG in temp sichern.
153
         in   temp, SREG       
154
155
                                    ; Vergleichswert 
156
        ldi     temp1, high( 8000 - 1 )
157
        out     OCR1AH, temp1
158
        ldi     temp1, low( 8000 - 1 )
159
        out     OCR1AL, temp1
160
                                    ; CTC Modus einschalten
161
                                    ; Vorteiler auf 1
162
        ldi     temp1, ( 1 << WGM12 ) | ( 1 << CS10 )
163
        out     TCCR1B, temp1
164
 
165
        ldi     temp1, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
166
        out     TIMSK, temp1
167
         
168
 
169
         out SREG, temp        ; Die Register SREG und temp wieder
170
         pop temp              ; herstellen
171
         reti
172
 
173
int1_handler:
174
         push temp             ; Das SREG in temp sichern. 
175
         in   temp, SREG       
176
 
177
         ldi     temp1, ( 0 << CS12 ) | ( 0 << CS11 ) | ( 0 << CS10 )  ;Timer anhalten
178
         out     TCCR1B, temp1
179
 
180
         out SREG, temp        ; Die Register SREG und temp wieder
181
         pop temp              ; herstellen
182
         reti
183
184
185
timer1_compare:
186
187
      push    temp1               ; temp 1 sichern
188
        in      temp1,sreg          ; SREG sichern
189
 
190
        inc     SubCount            ; Wenn dies nicht der 10. Interrupt
191
        cpi     SubCount, 10        ; ist, dann passiert gar nichts
192
        brne    end_isr
193
194
195
                                ; Überlauf von SubCount 
196
        clr     SubCount            ; SubCount rücksetzen
197
        inc     Hundert             ; plus 1 Hundertstel
198
        cpi     Hundert, 100        ; sind 60 Sekunden vergangen?
199
        brne    Ausgabe             ; wenn nicht kann die Ausgabe schon
200
                                    ; gemacht werden
201
202
203
                                    ; Überlauf von Hundertstel 
204
        clr     Hundert             ; Hundertstel wieder auf 0 und dafür
205
        inc     Sekunden            ; plus 1 Sekunde
206
        cpi     Sekunden, 60        ; sind 60 Minuten vergangen ?
207
        brne    Ausgabe             ; wenn nicht, -> Ausgabe
208
209
210
                                    ; Überlauf von Sekunden
211
        clr     Sekunden             ; Sekunden zurücksetzen und dafür
212
        inc     Minuten             ; plus 1 Minute
213
        cpi     Minuten, 9          ; nach 9 Minuten, die Minutenanzeige
214
        brne    Ausgabe             ; wieder zurücksetzen -> Minuten nur ein Segment, größte Zahl ist 9
215
 
216
                                    ; Überlauf von Minuten
217
        clr     Minuten             ; Minuten rücksetzen
218
219
220
221
end_isr:
222
 
223
        out     sreg,temp1          ; sreg wieder herstellen
224
        pop     temp1
225
        reti                        ; das wars. Interrupt ist fertig
226
227
228
229
Ausgabe:
230
231
        ldi     flag,1              ; Flag setzen, updaten
232
233
234
235
236
Codes:
237
    .db  0b11000000, 0b11111001     ; 0: a, b, c, d, e, f
238
                                    ; 1: b, c
239
    .db  0b10100100, 0b10110000     ; 2: a, b, d, e, g
240
                                    ; 3: a, b, c, d, g
241
    .db  0b10011001, 0b10010010     ; 4: b, c, f, g
242
                                    ; 5: a, c, d, f, g
243
    .db  0b10000010, 0b11111000     ; 6: a, c, d, e, f, g
244
                                    ; 7: a, b, c
245
    .db  0b10000000, 0b10010000     ; 8: a, b, c, d, e, f, g
246
                                    ; 9: a, b, c, d, f, g 
247
 
248
.DSEG
249
NextDigit:   .byte 1         ; Bitmuster für die Aktivierung des nächsten Segments
250
NextSegment: .byte 1         ; Nummer des nächsten aktiven Segments
251
Segment0:    .byte 1         ; Ausgabemuster für Segment 0
252
Segment1:    .byte 1         ; Ausgabemuster für Segment 1
253
Segment2:    .byte 1         ; Ausgabemuster für Segment 2
254
Segment3:    .byte 1         ; Ausgabemuster für Segment 3 
255
Segment4:    .byte 1         ; Ausgabemuster für Segment 4

von philipp (Gast)


Lesenswert?

schau mal bei den Lcd-routines, ziemlich weit unten ist sowas, musst du 
halt umbasteln für 7 segment. das dröselt die zahl in 2 bzw 3 stellen 
auf. kriegst du die 7 segment schon angesteuert?

von Karl H. (kbuchegg)


Lesenswert?


von oldmax (Gast)


Lesenswert?

Hi
Auch die bessere Version funktioniert sicherlich auch nicht....
Also deine Programmschleife solltest du dir mal genauer ansehen. da ist 
es möglich, das du direkt in die ISR rauscht, und das ohne irgend eine 
RCALL. Irgendwann kommt dann ein netter RETI und schwups, ist dein Stack 
im Nirwana...
Also, bei einem Assemblerprogramm solltest du folgendes beherzigen:
Viele kleine Routinen und auch das EVA-Prinzip ! (Eingabe, Verarbeitung, 
Ausgabe)
Die Initialisierung der einzelnen Module ( IO, Timer, UART, Defaults 
etc.) in einzelne kleine Routinen gepackt und mit RCALL aufgerufen macht 
dein Programm übersichtlich.
In der Programmschleife ist die letzte Anweisung ein "RJmp MainLoop" !
Danach kannst du die Routinen für die einzelnen Aufgaben in 
Unterprogramme fassen.
Wenn du mit Assembler arbeitest und eine serielle Schnittstelle hast, 
kannst du mit OpenEye dir zur Laufzeit die Variablen ansehen. OpenEye 
ist ein kleines PC Programm von mir. Hab ich hier irgendwo mal 
veröffentlicht. Mir hilft es immer wieder, wenn ich nicht verstehe, 
warum der µC anderer Meinung ist wie ich.
Zu deinem Programm vielleicht noch ein Tip:
Einlesen der Eingänge und ablegen dr IO-Bits in einer Variablen. Danach 
eine Flanke bilden, welche Eingänge sich geändert haben. Den Zeitwert 
packst du in eine weitere Variable, die du im Msek.Takt hochzählst. 
Keine Angst, mit 8 MHz ist dein µC locker in der Lage...
Mit einer 32-Bit Division (findest du auch hier...) setzt du dann die 
Zahlenwerte
Zum Verständnis:
Zähler Integerwert: 34229  ( reichen 64 Sek. dann kannst du so hoch 
auflösen)
Ziffern bilden
Wert /10 ergibt 3422 Rest 9- Stelle 1
Wert /10 ergibt 342 Rest 2- Stelle 2
Wert /10 ergibt 34 Rest 2- Stelle 3
Wert /10 ergibt 3 Rest 4- Stelle 4
Wert 3 = stelle 5
Ein Variablenblock Matrix_0 bis Matrix_9 setzt du mit den Defautwerten 
für die 7 Segmentanzeige. Bitmuster 0-7 = Segment a-g
Über die berechnete Zahl und der Adressierung über ein Doppelregister 
mit dem Offset der Zahl erhälst du den 7 Segmentcode.
etwa so:
1
         LDS      Reg_A, Wert_1
2
         LDI      Reg_B, 0
3
         LDI  XL,LOW(Matrix_0)  ; x-Pointer 1.Matrixwert
4
  LDI  XH,HIGH(Matrix_0)
5
  ADD  XL, Reg_A
6
  ADC  XH, Reg_B    ; zur Matrixadresse addieren
7
         LD       Reg_A, x
8
         STS      Seg_Code_1,Reg_A
Ist nur ein kleiner Auszug, aber mit ein wenig Überlegung wirst du 
erkennen, das es ganz leicht ist, damit eine Multiplex-Routine für die 
Ausgabe zu füttern. Die Ausgabe der Variablen Seg_Cod 1 - Seg_Code5 habe 
ich dann in den Timer gepackt und rufe im mSek. Takt diese Routine zur 
Ausgabe auf. Dadurch ist die Anzeige unabhängig von Programmlaufzeiten 
und "flimmert" nicht. Wohlgemerkt, die Aufbereitung der 7 Segment-Codes 
erfolgt im normalen Programm.
Gruß oldmax

von Christian (Gast)


Angehängte Dateien:

Lesenswert?

philipp schrieb:
> schau mal bei den Lcd-routines, ziemlich weit unten ist sowas, musst du
> halt umbasteln für 7 segment. das dröselt die zahl in 2 bzw 3 stellen
> auf. kriegst du die 7 segment schon angesteuert?

Vielen Dank, das hatte ich übersehen. Ich glaube ich hab es jetzt 
geschafft die Zahlen in 2 Stellen aufzuspalten.
Mit der Ansteuerung der Segmente hapert es noch an der Codetabelle, aber 
dazu später mehr.


Erstmal vielen Dank oldmax das du dir die Mühe gemacht hast eine so 
ausführliche Antwort zu schreiben.

oldmax schrieb:
> Hi
> Auch die bessere Version funktioniert sicherlich auch nicht....

Hab das besser auf die Lesbarkeit bezogen, nicht auf den Inhalt :)

oldmax schrieb:
> Also, bei einem Assemblerprogramm solltest du folgendes beherzigen:
> Viele kleine Routinen und auch das EVA-Prinzip ! (Eingabe, Verarbeitung,
> Ausgabe)
> Die Initialisierung der einzelnen Module ( IO, Timer, UART, Defaults
> etc.) in einzelne kleine Routinen gepackt und mit RCALL aufgerufen macht
> dein Programm übersichtlich.

Ich hab das Programm jetzt mal ein bisschen aufgeräumt und alles schön 
wie im Informatikunterricht gelernt in Unterprogramme aufgeteilt.
Wenn ich mir das so betrachte sah das erste Programm ja unter aller 
Kanone aus.

oldmax schrieb:
> Wenn du mit Assembler arbeitest und eine serielle Schnittstelle hast,
> kannst du mit OpenEye dir zur Laufzeit die Variablen ansehen. OpenEye
> ist ein kleines PC Programm von mir. Hab ich hier irgendwo mal
> veröffentlicht. Mir hilft es immer wieder, wenn ich nicht verstehe,
> warum der µC anderer Meinung ist wie ich.

Serielle Schnittstelle habe ich, nur leider kein Kabel. Mit dem 
AVRStudio kann ich mir im Debuggmodus doch auch die Register und die 
darin enthaltenen Variablen ansehen oder meinst du etwas anderes?

oldmax schrieb:
> Mit einer 32-Bit Division (findest du auch hier...) setzt du dann die
> Zahlenwerte
> Zum Verständnis:
> Zähler Integerwert: 34229  ( reichen 64 Sek. dann kannst du so hoch
> auflösen)
> Ziffern bilden
> Wert /10 ergibt 3422 Rest 9- Stelle 1
> Wert /10 ergibt 342 Rest 2- Stelle 2
> Wert /10 ergibt 34 Rest 2- Stelle 3
> Wert /10 ergibt 3 Rest 4- Stelle 4
> Wert 3 = stelle 5

Theoretisch würden 64 Sekunden reichen, ich würde jedoch gerne die Zeit 
nach folgendem Schema anzeigen:
[Minuten]---[Sekunden][Sekunden]---[Hundertstel][Hundertstel]
Die Klammern sollen jeweils einen 7Segment-Baustein darstellen.

oldmax schrieb:
> Ein Variablenblock Matrix_0 bis Matrix_9 setzt du mit den Defautwerten
> für die 7 Segmentanzeige. Bitmuster 0-7 = Segment a-g
> Über die berechnete Zahl und der Adressierung über ein Doppelregister
> mit dem Offset der Zahl erhälst du den 7 Segmentcode.
> etwa so:
>
1
>       LDS      Reg_A, Wert_1
2
>       LDI      Reg_B, 0
3
>       LDI  XL,LOW(Matrix_0)  ; x-Pointer 1.Matrixwert
4
>   LDI  XH,HIGH(Matrix_0)
5
>   ADD  XL, Reg_A
6
>   ADC  XH, Reg_B    ; zur Matrixadresse addieren
7
>       LD       Reg_A, x
8
>       STS      Seg_Code_1,Reg_A
9
>

Ich hab das mal versucht in den Code einzubauen, jedoch müsste ich dann 
wegen der Padding Bytes das Doppelregister mit dem zweifachen Wert laden 
oder?

oldmax schrieb:
> Ist nur ein kleiner Auszug, aber mit ein wenig Überlegung wirst du
> erkennen, das es ganz leicht ist, damit eine Multiplex-Routine für die
> Ausgabe zu füttern. Die Ausgabe der Variablen Seg_Cod 1 - Seg_Code5 habe
> ich dann in den Timer gepackt und rufe im mSek. Takt diese Routine zur
> Ausgabe auf. Dadurch ist die Anzeige unabhängig von Programmlaufzeiten
> und "flimmert" nicht. Wohlgemerkt, die Aufbereitung der 7 Segment-Codes
> erfolgt im normalen Programm.

Hier komme ich einfach nicht weiter.
Ich verstehe nicht wie ich es anstellen soll das der korrekte 
LED-Baustein angeschaltet und gleichzeigt das richtige Muster ausgegeben 
wird. Das muss auch noch an allen 5 Bausteinen nacheinander und richtig 
passieren.
Es wäre fantastisch wenn du mir vielleicht nochmal ein kleines 
Codeschnipsel liefern könntest und ich mir dann überlegen kann wie ich 
es lösen könnte.
Bei einer Facharbeit soll man ja schließlich etwas lernen.

Ein weiteres Problem das sich auftut ist die Codetabelle mit den 
Bitmustern für die Anzeige von Zahlen.
So wie ich die Tabelle jetzt aufgebaut habe stimmen die Muster nur wenn 
alles an PortD ausgegeben wird. Bei mir sind aber PD2 und PD3 durch die 
Interrupteingänge belegt und ich muss die letzten beiden Anschlüsse auf 
PortB legen.

a = PD0
b = PD1
c = PD4
d = PD5
e = PD6
f = PD7
g = PB0
h = PB1 (Dezimalpunkt; wobei ich den Anschluss eigentlich nicht 
bräuchte)

Wie berücksichtige ich diese Änderung in der Codetabelle.

Ich füge nochmal meinen etwas überarbeiteten Code hinzu, wobei dies kein 
fertiger Code ist, sondern nur meine Ideen und meinen jetztigen Stand 
darstellt.

Tut mir Leid das ich euch so mit Fragen bombardiere, aber ich komme an 
diesem Punkt einfach nicht mehr weiter.

Beste Grüße
Christian
1
.include "m8def.inc"
2
3
.def zero       = r1
4
.def h_Einer    = r2
5
.def h_zehner   = r3
6
.def sek_Einer  = r4
7
.def sek_Zehner = r5
8
.def min_Einer  = r6
9
.def temp  = r16
10
.def temp1 = r17
11
.def temp2 = r18
12
.def temp3 = r19
13
.def Flag  = r20
14
 
15
.def SubCount = r21           
16
.def Hundert  = r22
17
.def Sekunden = r23
18
.def Minuten  = r24
19
.def test     = r25
20
 
21
.org 0x000
22
         rjmp main            ; Reset Handler
23
.org INT0addr
24
         rjmp int0    ; Ext. Interrupt0 Handler
25
.org INT1addr
26
         rjmp int1    ; Ext. Interrupt1 Handler
27
.org OC1Aaddr
28
           rjmp timer1_compare ;Timer1 Handler
29
.org OVF0addr
30
           rjmp multiplex   ;Multiplexfunktion Handler
31
32
 
33
main:                               ; Hauptprogramm
34
 
35
    ldi temp, LOW(RAMEND)
36
    out SPL, temp
37
    ldi temp, HIGH(RAMEND)      ;Stackpointer intialisieren
38
    out SPH, temp
39
        
40
    rcall ausgang 
41
    
42
    rcall interrupt
43
44
    rcall stoppuhr
45
46
    rcall frequenztimer
47
                                      
48
    rcall clr_uhr
49
50
    sei                         ; Interrupts allgemein aktivieren
51
52
53
loop:   rjmp loop
54
55
ausgang:
56
    ldi temp, 0b11110011        ;Port D bis auf PD2 und PD3 ist Ausgang 
57
                                ;Anzeige belegt PD0, PD1, PD4, PD5, PD6, PD7, PB1, PB2
58
        out DDRD, temp
59
 
60
        ldi temp, 0b00000110       ;PB1 und PB2 sind Ausgang
61
        out DDRB, temp
62
63
        ldi temp, 0xFF            ;Port C ist Ausgang
64
        out DDRC, temp
65
        ret
66
67
interrupt:
68
69
        ldi temp, 0b00001111 ; INT0 und INT1  auf steigende Flanke konfigurieren   
70
        out MCUCR, temp
71
72
        ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
73
        out GICR, temp          
74
        ret
75
76
stoppuhr:
77
                                    ; Vergleichswert 
78
        ldi     temp1, high( 8000 - 1 )
79
        out     OCR1AH, temp1
80
        ldi     temp1, low( 8000 - 1 )
81
        out     OCR1AL, temp1
82
                                    
83
                                    
84
        ldi     temp1, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
85
        out     TCCR1B, temp1
86
 
87
        ldi     temp1, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
88
        out     TIMSK, temp1
89
        ret
90
91
frequenztimer:     ;für die multiplexfrequenz
92
93
        ldi     temp, ( 1 << CS01 ) | ( 1 << CS00 )
94
        out     TCCR0, temp
95
 
96
        ldi     temp, 1 << TOIE0
97
        out     TIMSK, temp
98
        ret
99
clr_Uhr:
100
101
        clr     Hundert             ; Die Uhr auf 0 setzen
102
        clr     Sekunden
103
        clr     Minuten
104
        clr     SubCount
105
        clr     Flag                ; Flag löschen
106
        ret
107
start:
108
109
        ldi     temp1, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
110
        out     TCCR1B, temp1
111
        ret
112
stopp:
113
114
        ldi     temp1, ( 0 << CS12 ) | ( 0 << CS11 ) | ( 0 << CS10 )     ;Timer anhalten
115
        out     TCCR1B, temp1
116
        ret
117
118
ziffer_berechnen:
119
120
;** Zehner  **
121
122
         ldi   sek_Zehner, '0'-1  ; sek_Zehner mit '0'-1 vorladen
123
zehner:
124
         inc   sek_Zehner         ; erhöhen (somit ist nach dem ersten
125
                                  ; Durchlauf eine '0' in sek_Zehner)
126
         subi  Sekunde, 10        ; 10 abziehen
127
         brcc  zehner             ; ist dadurch kein Unterlauf enstanden?
128
                                  ; nein, dann zurück zu zehner
129
         subi  Sekunde, -10       ; 10 wieder dazuzählen, da die
130
                                  ; vorherhgehende Schleife 10 zuviel
131
                                  ; abgezogen hat
132
;** Einer **
133
134
         ldi   sek_Einer, '0'-1   ; sek_Einer mit '0'-1 vorladen
135
einer:
136
         inc   sek_Einer          ; erhöhen (somit ist nach dem ersten
137
                                  ; Durchlauf eine '0' in sek_Einer)
138
         subi  Sekunde, 1         ; 1 abziehen
139
         brcc  zehner             ; ist dadurch kein Unterlauf enstanden?
140
                                  ; nein, dann zurück zu einer
141
         subi  Sekunde, -1        ; 1 wieder dazuzählen, da die
142
                                  ; vorherhgehende Schleife 1 zuviel
143
                                  ; abgezogen hat
144
145
code_sek_einer_ausgeben:
146
147
         lds      temp, sek_Einer
148
         ldi      temp1, 0
149
         ldi  XL,LOW(Codes*2)  ; x-Pointer 1.Matrixwert
150
         ldi  XH,HIGH(Codes*2)
151
         add  XL, temp
152
         adc  XH, temp1    ; zur Matrixadresse addieren
153
         ld       temp, X
154
         sts      Seg_Code_3,temp
155
156
;****** das war nur ein Beispiel an der Einerstelle der Sekunde um zu sehen ob diese Idee richtig ist?******
157
158
159
160
161
162
multiplex:
163
; hier weiß ich nicht wie ich den richtigen LED-Baustein ansteuere und das passende Muster auf ihm ausgebe
164
         out PORTD , Seg_Code
165
;hier fehlt noch einiges
166
         reti      ; am schluss wieder zu der loop endloss-schleife springen 
167
int0:
168
         push temp             ; Das SREG in temp sichern.
169
         in   temp, SREG       
170
         
171
         rcall start           ;Timer starten
172
 
173
         out SREG, temp        ; Die Register SREG und temp wieder
174
         pop temp              ; herstellen
175
         reti
176
 
177
int1:
178
         push temp             ; Das SREG in temp sichern. 
179
         in   temp, SREG       
180
 
181
         rcall stopp           ;Timer stoppen
182
 
183
         out SREG, temp        ; Die Register SREG und temp wieder
184
         pop temp              ; herstellen
185
         reti
186
187
timer1_compare:
188
189
      push    temp1               ; temp 1 sichern
190
        in      temp1,sreg          ; SREG sichern
191
 
192
        inc     SubCount            ; Wenn dies nicht der 10. Interrupt
193
        cpi     SubCount, 10        ; ist, dann passiert gar nichts
194
        brne    end_isr
195
196
                                ; Überlauf von SubCount 
197
        clr     SubCount            ; SubCount rücksetzen
198
        inc     Hundert             ; plus 1 Hundertstel
199
        cpi     Hundert, 100        ; sind 60 Sekunden vergangen?
200
        brne    end_isr             ; wenn nicht kann die Ausgabe schon
201
                                    ; gemacht werden
202
203
                                    ; Überlauf von Hundertstel 
204
        clr     Hundert             ; Hundertstel wieder auf 0 und dafür
205
        inc     Sekunden            ; plus 1 Sekunde
206
        cpi     Sekunden, 60        ; sind 60 Minuten vergangen ?
207
        brne    end_isr             ; wenn nicht, -> Ausgabe
208
209
                                    ; Überlauf von Sekunden
210
        clr     Sekunden             ; Sekunden zurücksetzen und dafür
211
        inc     Minuten             ; plus 1 Minute
212
        cpi     Minuten, 9          ; nach 9 Minuten, die Minutenanzeige
213
        brne    end_isr             ; wieder zurücksetzen -> Minuten nur ein Segment, größte Zahl ist 9
214
 
215
                                    ; Überlauf von Minuten
216
        clr     Minuten             ; Minuten rücksetzen
217
        rjmp    loop
218
219
end_isr:
220
 
221
        out     sreg,temp1          ; sreg wieder herstellen
222
        pop     temp1
223
        rjmp    loop                ;
224
225
226
Codes:                               ; Die Codetabelle für die Ziffern 0 bis 9
227
                                     ; sie regelt, welche Segmente für eine bestimmte
228
                                     ; Ziffer eingeschaltet werden müssen
229
                                     ;KANN JEDOCH WEGEN PORTB NICHT VERWENDET WERDEN
230
           .db     0b11000000        ; 0: a, b, c, d, e, f
231
           .db     0b11111001        ; 1: b, c
232
           .db     0b10100100        ; 2: a, b, d, e, g
233
           .db     0b10110000        ; 3: a, b, c, d, g
234
           .db     0b10011001        ; 4: b, c, f, g
235
           .db     0b10010010        ; 5: a, c, d, f, g
236
           .db     0b10000010        ; 6: a, c, d, e, f, g
237
           .db     0b11111000        ; 7: a, b, c
238
           .db     0b10000000        ; 8: a, b, c, d, e, f, g
239
           .db     0b10010000        ; 9: a, b, c, d, f, g 
240
241
242
.DSEG
243
Seg_Code1:    .byte 1         ; Ausgabemuster für Segment 1
244
Seg_Code2:    .byte 1         ; Ausgabemuster für Segment 2
245
Seg_Code3:    .byte 1         ; Ausgabemuster für Segment 3
246
Seg_Code4:    .byte 1         ; Ausgabemuster für Segment 4
247
Seg_Code5:    .byte 1         ; Ausgabemuster für Segment 5

von Martin V. (oldmax)


Lesenswert?

Hi
>Ich hab das mal versucht in den Code einzubauen, jedoch müsste ich dann
>wegen der Padding Bytes das Doppelregister mit dem zweifachen Wert laden
>oder?

Das trifft nur auf das Codesegment zu, im SRam (DSeg) und auch im 
EERprom adressierst du jede Speicherzelle. Daher liegen die Variablen 
für den Segmentcode, ich nenne sie mal  Matrix_0 bis Matrix_9, auch im 
Bereich X+n (n =0 bis 9)
Daher kannst du, wenn du eine einstellige Zahl hast, diese über die 
Matrix zum 7-Segment-Code umsetzen.
Was in Assembler sicherlich nicht einfach ist, ist Mathematik 
anzuwenden. Auch ich tu mich da ein wenig schwer und hab deswegen auch 
ein bisschen von den richtigen Fachleuten abgeschaut... KHB hat irgendwo 
hier eine 32 Bit Division veröffentlicht, die ich mir auf eine 16 Bit 
Division umgekupfert habe.
1
;zuerst mal Register definieren
2
.Def    mSek_0  = r1    ; Zählregister 1/1000 in ISR
3
.Def    mSek_1  = r2    ; Zählregister für 1/100 Sekunden in ISR
4
.Def    msel_2  = r3    ; Zählregister für 1/10 Sekunden
5
.Def    Dekade  = r4    ; Register für Vergleich auf 10 in ISR
6
.def  Reg_AL  = r16
7
.def  Reg_AH  = r17
8
.def  Reg_BL  = r18
9
.def  Reg_BH  = r19
10
11
.def  Reg_CL  = r20
12
.def  Reg_CH  = r21
13
.Def    Reg:Cnt = r22
14
15
;-------------- Division 16 ---------------------
16
17
;********************************************************
18
;* Vorzeichenlose 16-Bit-Division               *
19
;* Reg_AH, Reg_AL = Reg_AH, Reg_AL / Reg_BH, Reg_BL  *
20
;* Reg_BH, Reg_BL = Rest        *
21
;******************************************************** 
22
23
Div_Word:
24
  clr Reg_CL   ; Hilfsregister
25
  clr Reg_CH
26
  ldi Reg_Cnt, 16  ; Schrittregister
27
div_Loop1: 
28
  LSL Reg_AL
29
  rol Reg_AH
30
31
  rol Reg_CL
32
  rol Reg_CH
33
  
34
  cp Reg_CL, Reg_BL
35
  cpc Reg_CH, Reg_BH
36
37
  brcs div_Loop2
38
  Sub Reg_CL, Reg_BL
39
  SBC Reg_CH, Reg_BH
40
41
  inc Reg_AL
42
div_Loop2: 
43
  dec Reg_Cnt
44
  brne div_Loop1
45
  mov Reg_BL, Reg_CL
46
  mov Reg_BH, Reg_CH
47
48
ret
49
;**********************************************
Nun vereinbarst du für jede darzustellende Ziffer eine Variable sowie 
eine für den entsprechenden Segmentcode.
Also
1
; die kleinen Werte in Register packen, da schnellerer Zugriff. 
2
; in ISR ist dies wichtig. Bei den gößeren Werten spielt es keine 
3
; Rolle mehr. Daher können hier auch Variablen benutzt werden.
4
Sekunde:     .Byte 1     ; erklärt sich selbst
5
Minute:      .Byte 1
6
MSek_Anz1:   .Byte 1     ; für 1/100 Sekunden
7
Msel_Anz2:   .Byte 1     ; für 1/10 Sekunden
8
Sek_Anz0:    .Byte 1     ; erklärt sich selbst
9
Sek_Anz1:    .Byte 1
10
Min_Anz0:    .Byte 1
11
Min_Anz1:    .Byte 1
12
; Darstellbereich mit 16 Bitzahl 65535 ergibt rd. 10 Minuten Stopzeit
13
; analog dazu Variablen für den Segmentcode
14
MSek_Code1:   .Byte 1     ; für 1/100 Sekunden
15
Msel_Code2:   .Byte 1     ; für 1/10 Sekunden
16
Sek_Code0:    .Byte 1     ; erklärt sich selbst
17
Sek_Code1:    .Byte 1
18
Min_Code0:    .Byte 1
19
Min_Code1:    .Byte 1
Sorry, wenn ich den Variablen nun andere Namen gegeben habe. Wichtig 
ist, das du das Prinzip verstehst.
Deine ISR löst mSek. auf. So ist es nicht schwer, diese auch zu zählen. 
Dafür habe ich Register geopfert, um ein paar Befehle zu sparen und mich 
möglichst kurz zu fassen.
Timer_ISR:
; verwendete Register und Statue auf Stack packen (Push Register)
; hier rufst du den Multiplexer für die Ausgabe auf. Ergibt ein
; gleichmäßiges Zeitraster. Durch unterschiedliche Zykluszeiten
; in der Programmschleife kann es zu flimmern der Anzeige kommen.
; hier ist das nicht der Fall und die Anzeige ist gleichmäßig.
   Inc    msek_0
   CP     mSek_0, Dekade   ; Dekade in einer Initialisierung den Wert 10
   BRLO   Timer_ISR_End    ; zuweisen
   CLR    mSek_0
; hier kannst du abfragen, ob dein Zeitzähler freigegeben ist.
; dann rufst du das Unterprogramm auf, welches nur die zu
; stoppende Zeit zählt. Mehr nicht !
; Bedenke, alle Register innerhalb einer ISR-Bearbeitung
; müssen auf den Stack. Auch die, die in einer von hier
; aufgerufenen Routine benutzt werden. Das hast du in deinem
; Programm nicht beachtet.
   INC    mSek_1
   CP     mSek_1, Dekade
   BRLO   Timer_ISR_End
   CLR    mSek_1
   INC    mSek_2
   CP     mSek_2, Dekade
   BRLO   Timer_ISR_End
   CLR    mSek_2
   LDS    Temp, Sekunde    ; Temp beliebiges Register > r15
   INC    Temp
   STS    Sekunde, Temp
   CPI    Temp, 60
   BRLO   Timer_ISR_End
   CLR    Temp             ; sekunden auf 0 sezen
   STS    Sekunde, Temp    ; und nochmal ablegen
   LDS    Minuten          ; und nun Minuten zählen usw.
   ...
Timer_ISR_End:
  ; Register und Status nu vom Stack holen ( POP register )
RETI
[/avrasm]
Ich hab hier mal die ISR mit ihren Möglichkeiten beschrieben.
Du veränderst zwar in der ISR bei freigegebenen Zeitzähler (Stoppuhr) 
die Werte, aber es reicht aus, die Berechnungen für die Anzeigewerte in 
der Hauptschleife durchzuführen. Das läuft nun folgendermaßen ab:
Gezählte Hundertstelsekunde durch 10 Teilen und Ergebnis für nächste 
Teilung bereitstellen. Der Rest gehört in die Variable mSek_Anz1.
So ermittelst du ert einmal einen richtigen Zahlenwert für die Anzeige.
Anschließend rufst du den Codierer auf und ermittelst anhand der 
errechneten Zahlen den Anzeigecode für die 7Segmentanzeige.
Das Unterprogramm zur Anzeige, ich nenn es mal Multiplexer, wird nun wie 
bereits angekündigt, in der ISR aufgerufen und holt sich nur den 
Codierten Teil für die Anzeige. Dazu brauchst du ein Byte, um die 
Segmente zu adressieren und ein Byte, um die anzuzeigende Stelle zu 
adressieren.
Nennen wir sie mal Seg_Pos und Seg_Cnt.
Seg_Pos hat nur ein Bit, welches durchgeschoben wird. Idealerweise ein 
Abbild des Registers, mit dem du die gemeinsamen Segmentanschlüsse der 
Ziffern beschaltest.
Als erstes setzt du diesen Port so, das alle Anzeigen ausgehen, also auf 
"0". Das verhindert Geisterzahlen...
Dann incrementierst du den Zähler Seg_Cnt, prüfst die Grenze und setzt 
evtl. diesen zurück. (beachte auch seg_Pos) Nun holst du über die 
Adressierung mittels X-Register und dem Seg_Cnt als Offset den 
bereitgestellten Code für die Anzeige. Schiebst dein Register Seg_Pos 
eins weiter auf die anzuzeigende Stelle und setzt dann deine Ausgabe, 
diesmal direkt innerhalb der ISR und nicht erst am Ende, wie es das 
EVA-Prinzip eigentlich vorgibt. Zuerst den Code und dann gibst du das 
Segment frei. Beim nächsten Aufruf ist dann die nächste Ziffer dran. Wie 
du siehst, ist Ausgabe vom Zähler getrennt und völlig unabhängig. 
Angezeigt werden die Zahlen, die durch Werte in den Anzeigevariablen 
stehen.
Also, dein Programmgerüst sollte wie folgt aussehen:
Definieren von Register und Variablen
Start: Initialisierungsaufrufe für IO, Timer,UART Defaults
Loop:
  Lesen IO
  Ereignisermittlung
  Lesen Schnittstelle ( Ringpuffer prüfen)
  Bearbeiten 1
  Bearbeiten 2
  etc.
RJMP Loop

Bereich Unterprogramme, mit Kommentarzeilen gut abheben. Die Reihenfolge 
ist individuell meine und nicht zwingend.

Zuerst Inits
Dann lt. Programmschleife
dann evtl. geschachtelte Unterprogramme
Dann ISR's

Und nun noch ein kleiner Hinweis zu OpenEye. Dieses Programm holt sich 
den Variablenbereich vom Controller und zeigt ihn an. Nicht im 
Simulator, sondern zur Laufzeit. Das was du da siehst, ist das, was der 
Controller auch im Bauch hat. So kannst du berechnete Zahlen prüfen. 
Dazu brauchst du halt die RS 232 mit RxD, TxD und GND beschaltet. 
OpenEye ist hier frei verfügbar. Grad bei Programmen im Assembler 
stolpert man doch sehr oft über die eigenen Beine. So setze ich mir 
Kontroll-Flags und kann anhand dieser den Ablauf verfolgen. Und Speicher 
hat der Atmega ja genug, da kann man ruhig mal so 40 -50 Variablen 
vergeben.
Ob Ergebnisse andere Programmiersprachen auch mit OpenEye zu testen 
sind, hab ich nicht geprüft, aber hier mal ein Hinweis: OpenEYe holt 
sich die Adresse der ersten Variable und verschickt soviel Bytes, wie in 
der Senderoutine hinterlegt sind. Wenn OpenEye darauf eingerichtet ist, 
werden diese Inhalte dann in entsprechendem gewünschten Format 
(Bitmuster, Zahl oder ASCII ) ausgegeben.
Nun denk ich, hast du erst mal genug Stoff.
Gruß oldmax

von Christian (Gast)


Lesenswert?

Vielen Dank für den neuen Stoff, oldmax :)
Da ich morgen wegen mündlichem Abi keine Schule hab werd ich mich heute 
Abend mal dahinterklemmen.
Es wird wohl eine lange Nacht werden, aber ich halte euch auf dem 
Laufenden...

Grüße
Christian

von Christian (Gast)


Lesenswert?

Habe den Code jetzt fast fertig :)
Er ist etwas anders geworden wie ich zuerst gedacht hatte, aber er 
scheint zu funktionieren.
Ich habe es nun so gelöst das zuerst alles intialisiert wird und das 
Programm dann in einer Endlosschleife auf den Interrupt der 
Lichtschranke wartet. Dieser startet den Timer1, der im CTC Modus so 
eingestellt ist, das alle Tausendstelsekunde ein Überlauf stattfindt. 
Bei 10 Überläufen wird die Hundertstelstelle um eins erhöht, bei 100 
Hundertstel die Sekundenstelle und bei 60 Sekunden die Minutenstelle. In 
dieser Zeit wird noch nichts angezeigt.
Der Interrupt der zweiten Lichtschranke stoppt den Timer1 und leitet die 
Berechnung vom Codemuster für die einzelnen Segmente ein. Diese werden 
dann im SRAM abgelegt. Erst nachdem alle Segmente berechnet sind, wird 
der Timer0 gestartet um das Multplexen zu übernehmen. Hier wird bei 
jedem Interrupt das Bit das die Transistoren steuert nach links 
verschoben und das Codemuster wird ausgegeben. Bei jedem Interrupt kommt 
eine neue Stelle dran.

Das ist erst einmal der Rohbau meines Codes der jetzt noch optimiert 
werden muss.

Jedoch habe ich noch ein paar Fragen besonders an oldmax, die ich mir 
einfach nicht beantworten kann. Könntest du bitte mal über den Code 
schauen und mir sagen ob das mit Z-Pointer und Codetabelle so klappen 
kann?
Im Debuggmodus des AVR-studios scheint es zu funktionieren, wobei ich 
mir nicht sicher bin ob dort das SRAM mit beachtet wird.
Desweiteren fällt mir dabei auf das sich das SREG wie wild 
verändert.(besonders beim cpi-Befehl) Müsste ich das öfters mit push und 
pop sichern?

Die Ansteuerung zur Auswahl des richtigen LED-Bausteins, sowie die 
Routine zur Berechnung der Anzeigewerte sind sehr eigentümlich und 
befehlsintensiv geworden, aber sie scheinen zu funktionieren. Ist die 
Anzeige Routine vielleicht zu lang für die ISR?


Mein größtes Problem ist jedoch das ich das Muster für die Zahlen nicht 
komplett auf Port D ausgeben kann, sondern noch zwei Pins von PortB 
benötige, da am PortD noch die zwei Interrupts hängen.
Hat hierfür jemand einen Vorschlag, den ich habe keinen blassen Schimmer 
wie ich dieses Problem lösen ober umgehen soll.

Zu guter Letzt nochmal mein Code.

Nicht schön, aber selten :)
1
.include "m8def.inc"
2
3
.def null        = r1
4
.def ausschalten = r2
5
.def temp        = r16
6
.def Zehner      = r17
7
.def zaehler     = r18
8
.def flag        = r19
9
.def SubCount    = r20           
10
.def Hundertstel = r21
11
.def Sekunden    = r22
12
.def Minuten     = r23
13
.def schalter    = r24
14
15
 
16
.org 0x000
17
         rjmp main             ; Reset Handler
18
.org INT0addr
19
         rjmp interrupt0       ; Ext. Interrupt0 Handler
20
.org INT1addr
21
         rjmp interrupt1       ; Ext. Interrupt1 Handler
22
.org OC1Aaddr
23
           rjmp timer1_compare ;Timer1 Handler
24
.org OVF0addr
25
           rjmp anzeige1        ;Timer0 Multiplexfunktion Handler
26
27
 
28
main:  ; Hauptprogramm
29
    ldi temp, LOW(RAMEND)
30
    out SPL, temp
31
    ldi temp, HIGH(RAMEND) ;Stackpointer intialisieren
32
    out SPH, temp
33
        
34
    rcall InOut            ;IO-Register einstellen
35
    rcall interrupts       ;Interrupts einstellen
36
    rcall stoppuhr         ;Stoppuhr(Timer1) einstellen
37
    rcall multiplextimer   ;Multiplexfrequenz(Timer0) einstellen                                  
38
    rcall clear            ;Stoppuhr auf Null stellen und Variablen auf null setzen
39
    sei                    ; Interrupts allgemein aktivieren
40
41
loop:   
42
    cpi flag,1
43
    breq berechnung      ;bei gesetzter 1 vom Stopp-interrupt berechnung starten
44
    cpi flag,2
45
    breq multiplex       ; bei gesetzter 2 von berechnungs routine anzeige starten
46
    rjmp loop            ; ansonsten Endlosschleife
47
48
49
;************************************UNTERPROGRAMME*********************************************
50
;***********************************************************************************************
51
52
multiplex:
53
    ldi  temp, ( 1 << CS01 ) | ( 1 << CS00 )  ;Timer/Frequenz aktivieren
54
    out  TCCR0, temp
55
    clr flag
56
    sei
57
    rjmp loop
58
59
;***********Steuerung der LED-Segmente und Anzeigen des im SRAM abgelegten Musters**********
60
anzeige1:   
61
    cpi zaehler, 1           ;vergleichen zähler mit 1
62
    brsh anzeige2            ;größer oder gleich 1 -> anzeige2
63
    out PORTC, ausschalten   ;ansonsten segemt ausschalten
64
    lds temp, Seg_Code1
65
    out PORTD, temp          ;muster für zahl ausgeben
66
    inc schalter             
67
    out PORTC, schalter      ;Segment einschalten
68
    inc zaehler              ;zähler erhöhen
69
    reti
70
anzeige2:   
71
    cpi zaehler, 2
72
    brsh anzeige3
73
    out PORTC, ausschalten
74
    lds temp, Seg_Code2
75
    out PORTD, temp
76
    lsl schalter
77
    out PORTC, schalter
78
    inc zaehler
79
    reti
80
anzeige3:    
81
    cpi zaehler, 3
82
    brsh anzeige4    
83
    out PORTC, ausschalten
84
    lds temp, Seg_Code3
85
    out PORTD ,temp
86
    lsl schalter
87
    out PORTC, schalter
88
    inc zaehler
89
    reti
90
anzeige4:     
91
    cpi zaehler, 4
92
    brsh anzeige5    
93
    out PORTC, ausschalten
94
    lds temp, Seg_Code4
95
    out PORTD ,temp
96
    lsl schalter
97
    out PORTC, schalter
98
    inc zaehler
99
    reti
100
anzeige5:      
101
    out PORTC, ausschalten
102
    lds temp, Seg_Code5
103
    out PORTD ,temp
104
    lsl schalter
105
    out PORTC, schalter
106
    clr zaehler
107
    clr schalter
108
    reti
109
;*****************Berechnung der Anzeigecodes für die Segmente******************************
110
berechnung:
111
    cli
112
    cpi Hundertstel, 10  ;Divident > 10 ?
113
    brlo ausgabe         ; ja -> nur Einer vorhanden, weiter zu ausgabe
114
    subi Hundertstel,10  ; nein: 10 abziehen
115
    inc Zehner           ; Zehner +1
116
    rjmp berechnung      ; solange bis Divident < 10 dann weiter mit Einer
117
ausgabe:
118
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
119
    ldi ZH,HIGH(Codes*2)
120
    add ZL, Zehner        ;Abstand Zehner dazu
121
    adc ZH, null          ; + Übertrag 16bit-Operation
122
    lpm                   
123
    sts Seg_Code2, r0     ;Code im SRAM ablegen
124
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
125
    ldi ZH,HIGH(Codes*2)
126
    add ZL, Hundertstel   ;Abstand Einer dazu
127
    adc ZH, null          ; + Übertrag 16bit-Operation
128
    lpm
129
    sts Seg_Code1 , r0    ;Code im Sram ablegen
130
    clr Zehner
131
berechnung1:
132
    cpi Sekunden, 10      ;Divident > 10 ?
133
    brlo ausgabe1         ; ja -> nur Einer vorhanden, weiter zu ausgabe
134
    subi Sekunden,10      ; nein: 10 abziehen
135
    inc Zehner            ; Zehner +1
136
    rjmp berechnung1      ; solange bis Divident < 10 dann weiter mit Einer
137
ausgabe1:
138
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
139
    ldi ZH,HIGH(Codes*2)
140
    add ZL, Zehner        ;Abstand Zehner dazu
141
    adc ZH, null          ; + Übertrag 16bit-Operation
142
    lpm                   
143
    sts Seg_Code4, r0     ;Code im SRAM ablegen
144
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
145
    ldi ZH,HIGH(Codes*2)
146
    add ZL, Sekunden      ;Abstand Einer dazu
147
    adc ZH, null          ; + Übertrag 16bit-Operation
148
    lpm
149
    sts Seg_Code3 , r0    ;Code im Sram ablegen
150
    clr Zehner
151
berechnung2:    
152
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
153
    ldi ZH,HIGH(Codes*2)
154
    add ZL, Minuten       ;Abstand Einer dazu, da mit mehr als 9 Minuten möglich sind
155
    adc ZH, null          ; + Übertrag 16bit-Operation
156
    lpm
157
    sts Seg_Code5 , r0    ;Code im Sram ablegen
158
    ldi flag,2
159
    rjmp loop
160
161
;**************************Unterprogramme von main*****************************************
162
InOut:
163
    ldi temp, 0b11110011        ;Port D bis auf PD2 und PD3 ist Ausgang 
164
                                ;Anzeige belegt PD0, PD1, PD4, PD5, PD6, PD7, PB1, PB2
165
    out DDRD, temp
166
 
167
    ldi temp, 0b00000110       ;PB1 und PB2 sind Ausgang
168
    out DDRB, temp
169
170
    ldi temp, 0b00011111         ;Port C ist Ausgang
171
    out DDRC, temp
172
    ret
173
174
interrupts:
175
176
    ldi temp, 0b00001111 ; INT0 und INT1  auf steigende Flanke konfigurieren   
177
    out MCUCR, temp
178
179
    ldi temp, (1<<INT0) | (1<<INT1) ; INT0 und INT1 aktivieren
180
    out GICR, temp          
181
    ret
182
183
stoppuhr:
184
                                    ; Vergleichswert 
185
        ldi     temp, high( 8000 - 1 )
186
        out     OCR1AH, temp
187
        ldi     temp, low( 8000 - 1 )
188
        out     OCR1AL, temp
189
                                    
190
                                    
191
        ldi     temp, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
192
        out     TCCR1B, temp
193
 
194
        ldi     temp, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
195
        out     TIMSK, temp
196
        ret
197
198
199
200
multiplextimer:     ;für die multiplexfrequenz
201
202
        ldi     temp, ( 0 << CS01 ) | ( 0 << CS00 ) | ( 0 << CS02 )
203
        out     TCCR0, temp
204
 
205
        ldi     temp, 1 << TOIE0
206
        out     TIMSK, temp
207
        ret
208
clear:
209
210
    clr     Hundertstel             ; Die Uhr auf 0 setzen
211
    clr     Sekunden
212
    clr     Minuten
213
    clr     SubCount
214
    clr     Flag                ; Flag löschen
215
    clr     null
216
    clr     ausschalten
217
    clr     zaehler
218
    clr     Zehner
219
    clr     schalter
220
    ret
221
222
;****************************EXTERNE INTERRUPTS********************************
223
;******************************************************************************
224
interrupt0:
225
         push temp             ; Das SREG in temp sichern.
226
         in   temp, SREG       
227
         
228
         rcall start           ;Timer starten
229
 
230
         out SREG, temp        ; Die Register SREG und temp wieder
231
         pop temp              ; herstellen
232
         reti
233
 
234
interrupt1:
235
         push temp             ; Das SREG in temp sichern. 
236
         in   temp, SREG       
237
 
238
         rcall stopp           ;Timer stoppen
239
         ldi flag,1            ;Berechnung einleiten
240
241
         out SREG, temp        ; Die Register SREG und temp wieder
242
         pop temp              ; herstellen
243
         reti
244
245
246
;********************************Unterprogramme von Interrupts******************************
247
start:
248
249
        ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
250
        out     TCCR1B, temp
251
        ret
252
stopp:
253
254
        ldi     temp, ( 0 << CS12 ) | ( 0 << CS11 ) | ( 0 << CS10 )     ;Timer anhalten
255
        out     TCCR1B, temp
256
        ret
257
258
;*****************************Timer1 Überlauf/ STOPPUHR**************************************
259
timer1_compare:
260
261
        push    temp                ; temp 1 sichern
262
        in      temp,sreg           ; SREG sichern
263
 
264
        inc     SubCount            ; Wenn dies nicht der 10. Interrupt
265
        cpi     SubCount, 10        ; ist, dann passiert gar nichts
266
        brne    ende
267
268
                                ; Überlauf von SubCount 
269
        clr     SubCount            ; SubCount rücksetzen
270
        inc     Hundertstel             ; plus 1 Hundertstel
271
        cpi     Hundertstel, 100        ; sind 60 Sekunden vergangen?
272
        brne    ende                ; wenn nicht kann die Ausgabe schon
273
                                    ; gemacht werden
274
275
                                    ; Überlauf von Hundertstel 
276
        clr     Hundertstel             ; Hundertstel wieder auf 0 und dafür
277
        inc     Sekunden            ; plus 1 Sekunde
278
        cpi     Sekunden, 60        ; sind 60 Minuten vergangen ?
279
        brne    ende                ; wenn nicht, -> Ausgabe
280
281
                                    ; Überlauf von Sekunden
282
        clr     Sekunden             ; Sekunden zurücksetzen und dafür
283
        inc     Minuten             ; plus 1 Minute
284
        cpi     Minuten, 9          ; nach 9 Minuten, die Minutenanzeige
285
        brne    ende                ; wieder zurücksetzen -> Minuten nur ein Segment, größte Zahl ist 9
286
                                   
287
        clr     Minuten             ; Minuten rücksetzen
288
289
        out     sreg,temp           ; sreg wieder herstellen
290
        pop     temp            ; Minuten rücksetzen
291
        reti
292
293
ende:
294
 
295
        out     sreg,temp          ; sreg wieder herstellen
296
        pop     temp
297
        reti                
298
299
Codes: ;Tabelle für die Codes                           
300
.db 0b11000000,0b11111001,0b10100100,0b10110000,0b10011001,0b10010010,0b10000010,0b11111000,0b10000000,0b10010000
301
                              
302
303
.DSEG
304
Seg_Code1:    .byte 1         ; Ausgabemuster für Segment 1
305
Seg_Code2:    .byte 1         ; Ausgabemuster für Segment 2
306
Seg_Code3:    .byte 1         ; Ausgabemuster für Segment 3
307
Seg_Code4:    .byte 1         ; Ausgabemuster für Segment 4
308
Seg_Code5:    .byte 1         ; Ausgabemuster für Segment 5

von Karl H. (kbuchegg)


Lesenswert?

Für meinen Geschmack konfigurierst du da im laufenden Betrieb etwas zu 
viel die Timer um. Sehr oft führt das dazu, dass man sich irgendwo bei 
den Register verhaut.

zb bist du mit deinem TIMSK etwas auf Kriegsfuss. Mag aber auch daran 
liegen, dass es manchmal nicht wirklich ratsam ist, den Code zu sehr 
durch Hin und Herspringen bzw. viele rcalls zu zerpflücken. Man verliert 
dann sehr schnell den Überblick, welches Konfigurationsregister wo 
manipuliert wird.

Ich würde:

die Timerinitialisierungen an den Anfang vorziehen anstelle da am Anfang 
Unterfunktionen aufzurufen.

die Timer auch ständig durchlaufen lassen und nicht starten/stoppen.
Auf die Art hast du eine ständige Anzeige, durch den stets laufenden 
Multiplex-Interrupt und damit auch automatisch eine Rückmeldung ob die 
Uhr gerade läuft oder nicht.

Die Stoppuhr Funktionalität würde ich mit einer zusätzlichen Variable 
lösen, die einfach nur aussagt, ob die Uhr gerade laufen soll oder 
nicht.
In den Lichtschrankeninterrupts wird diese Variable auf 0 oder 1 gesetzt 
und der Timer Interrupt der Uhr prüft einfach ob die Variable 1 ist und 
wenn nicht, dann zählt die Uhr im Timerinterrupt einfach nicht hoch.

In der Hauptschleife läuft ständig die Umsetzung der Zeitvariablen auf 
die Anzeigestellen und der Multiplex-Interrupt läuft auch einfach nur 
ständig durch.

Den Anzeigecode könnte man etwas straffen und die vielen eingestreuten 
ret loswerden, das ist aber jetzt noch nicht so wichtig.
Was du dir abgewöhnen solltest, ist zb das Ende vom Timerinterrupt. Da 
hast du 2 mal denselben Aussteigecode. Das sollte nicht sein, da wirst 
du irgendwann darüberstolpern. Es gibt nur 1 Austeigecode aus der 
Funktion, und der wird in allen Fällen genommen.

Und oh, den Anzeigecode ab anzeige1: das ist eine Interrupt Funktion. Du 
musst das SREG sichern und wiederherstellen. Und spätestens jetzt rächt 
es sich, dass nicht alle Pfade an einem einzigen reti wieder 
zusammenlaufen.

von Karl H. (kbuchegg)


Lesenswert?

Welche Möglichkeit hat eigentlich der Benutzer, die Stoppuhr wieder auf 
0 zu stellen für die nächste Messung?

von Martin V. (oldmax)


Lesenswert?

Hi
Wenn es nicht grad "Deine" Facharbeit wäre.....
Also, so ganz hast du mich noch nicht verstanden. Alle Initialisierungen 
werden nur EINMAL gemacht. Danach sollte dein Timer, wie du es auch 
schreibst, in mSek.Takt einen Interrupt auslösen. Nun zählst du mSek ^0, 
mSek ^1, msek ^2, Sekunden, Minuten und wenns interessiert bis hin zu 
Jahren.
Da im Bereich der mSek. möglichst schneller Code sein sollte, verwende 
ich da Register, die ausschließlich mSek. zählen. Danach nehme ich 
Variablen. Ob nun 1 mal in der Sekunde eine Variable zusätzlich geladen 
und wieder zurückgespeichert wird, ist so ziemlich egal. Die 
Programmzeit erhöht sich da lediglich um ca. 4 Taktzyklen. ( bei 8 MHz 
0,5 µSek )
Dann mußt du bei Push und POP auch konsequent sein und verwendete 
Register sichern. Klar, das Register Temp nutzt du nicht ein zweitesmal 
in der ISR, so das dein SREG vermutlich auch wieder mit OUT 
zurückgeschrieben wird. Aber, rufst du in der ISR Unterprogramme auf, so 
sind diese darin verwendeten Register ebenfalls zu sichern. Du 
erweiterst ja nur die ISR.
@KHB
Der Tipp mit den kleinen Unterprogrammen ist schon ernst gemeint.

Wenn man eine Funktion zusammenfassen kann, sollte man es auch tun. 
Dadurch begrenzt sich auch die Fehlersuche. Und wenn man es richtig 
macht, behält man auch den Überblick.
Solche kleinen Routinen drucke ich mir bspw. aus. Normalerweise paßt 
eine Routine auf ein DinA4 Blatt. Diese kann man dan abheften und 
gezielt nachschlagenn nebeneinanderlegen oder was auch immer. Klar, wenn 
man nur im Monitor hin und herscrollt, da gibt's dann schon mal ein 
Problem mit dem Durchblick.
Auch hilft ein Programmablaufplan. Da sieht man dann auch, welche 
Subroutinen aus einer ISR heraus aufgerufen werden und ob deshalb die 
Register gesichert werden müssen.
Ich hab auch nicht verstanden, warum beim stoppen der Zeit die Anzeige 
nicht funktionieren soll. Den Hinweis, den Aufruf dafür in die ISR 
einzubinden und im msek. Takt den Multiplexer aufzurufen hatte seinen 
Grund. Ich hab mit 8 MHz eine saubere Anzeige erzielt. Warum sollte dies 
bei dir anders sein. Das Umsetzen der Anzeigencodes machst du im 
Programm. Und benenne deine Routinen ruhig mit Init_IO. So hast du eine 
klare Aussage.
Du hast ja schon einen Ansatz mit einer Variablen Flag gemacht. Geh doch 
einen Schrit weiter und nehme die einzelnen Bits. Ich nenne solche 
programmsteuernden Variablen Prg_Ctrl.
1
Prg_Ctrl:  .Byte 1         ; Bit 0  Zeitzähler Freigabe starten
2
                           ; Bit 1  Zeitzähler läuft
3
                           ; Bit 2  Zeitzähler ist gestoppt
4
                           ; Bit 3  Freigabe Zeitzähler Reset
5
                           ; Bit 4  Reserve für Zwischenzeit...
6
                           ; Bit 5  Reserve
7
                           ; Bit 6  Reserve
8
                           ; Bit 7  Reserve
9
weitere Variablen.....
10
[/avrsam]
11
12
In der Timer ISR fragst du lediglich im Bereich mSek ^2 ab, ob Bit 1 gesetzt ist
13
[avrasm]
14
  Lds   Reg_a, Prg_Ctrl
15
  Andi  Reg_a, 0b00000010
16
  BREQ  Weiter_x
17
  RCALL Stopp_Uhr           ; Aufruf der Stoppuhr
18
weiter_x:        
19
  ....

Das Bit Freigabe starten brauchst du bspw. um die 1. Lichtschranke 
freizugeben, damit nicht ein zufällig durchgewehtes Blatt dem Zähler die 
Freigabe gibt. Also eine Eingabe für Startfreigabe.
Um deine Anzeige wieder zurückzusetzen, brauchst du auch das Signal 
"Zähler gestoppt". Es gibt andere Bedingungen, wie z.B. die 
Tasteneingabe Startfreigabe, die nur einmal behandelt werden darf, also, 
wo du die Flanke brauchst, um dieses dann wieder nach Bearbeitung durch 
Löschen des Flankenbits zu quittieren. In diesem Fall setzt du das Bit 0 
in Prg_Ctrl und löscht das Flankenbit der Taste.
(Zu Flankenerkennung ist hier auch schon eine Menge geschrieben worden. 
Sollte dir das nicht klar sein, frag halt nochmal. )
Daher wirst du immer wieder über eine Und-Verknüpfung ein einzelnes Bit 
aus einem Byte herausmaskieren müssen, um den Status abzufragen. Schlagt 
mich nicht, es gibt glaube ich auch eine TST - Anweisung. Aber alle 
Assemblerbefehle hab ich nicht im Kopf., dafür ist aber die Hilfe in AVR 
Studio da ganz gut bestückt.
Gruß oldmax

von Martin V. (oldmax)


Lesenswert?

Hi
Noch ein Nachtrag:
1
interrupt0:
2
         push temp             ; Das SREG in temp sichern.
3
         in   temp, SREG       
4
         
5
         rcall start           ;Timer starten
6
 
7
         out SREG, temp        ; Die Register SREG und temp wieder
8
         pop temp              ; herstellen
9
         reti
Das ist deine ISR. Du sicherst Temp und das SREG in Temp. Temp hat hier 
bis ende der ISR scheinbar keine Änderung und so speichert du das SREG 
direkt zurück. Wär kein Problem, wenn da nicht dieser RCALL wäre....
1
;***************Unterprogramme von Interrupts**************************
2
start:
3
4
        ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
5
        out     TCCR1B, temp
6
        ret
 Und du schreibst auch noch, das diese Routine aus der ISR aufgeruffen 
wird, aber hast nicht bedacht, das Temp hier überschrieben wird. Was 
glaubst du, steht anschließend in deinem SREG ?
Gruß oldmax

von Vuvuzelatus (Gast)


Lesenswert?

Am besten reserviert man sich ein eigenes Register nur für das Sichern 
und Wiederherstellen des SREG in ISRs. Erfreulicherweise braucht man 
gewöhnlich nur eines (*), das man für alle ISRs verwenden kann. Da man 
nur die Instruktionen 'in' und 'out' mit diesem Register benutzt, sind 
alle Register gleichermaßen für diesen Zweck geeignet, d. h. man kann 
auch eines der weniger wertvollen unteren Register dafür hernehmen.

(*) Es sei denn man lässt verschachtelte Interrupts zu, d. h. die 
Unterbrechung von Interrupthandlern durch andere Interrupts (oder auch 
denselben Interrupt). Das sollte man aber möglichst vermeiden - und es 
ist zum Glück auch eher selten wirklich nötig.

von Martin V. (oldmax)


Lesenswert?

Hi
Deinen Worten stimme ich nicht so blauäugig zu. Wenn du in einer ISR 
eine Anzeige multiplext, so wie ich es beschrieben habe und Eingänge per 
Interrupt abfragst bzw. bearbeitest, mußt du schon damit rechnen, das 
ein sehr kurzes Signal in der Timer-ISR unterschlagen wird, wenn du 
diese Interrupts nicht zuläßt. Dann brauchst du auch keine 
Signalerfassung mit Interrupt bearbeiten, wenn du dieses einfach 
ignorierst. Wieviel mehr Takte benötigt es denn, ein gerettetes SREG 
auch über das Register auf den Stack zu schreiben... auch nur 4 
Taktzyklen, 2 für Push und 2 für POP. Bei 0,5 µSek ist das grad mal 
1/2000 der ISR- Aufrufzeit von 1 mSek.
Also es spricht nichts gegen
1
  push temp             ; erst temp sichern.
2
  in   temp, SREG 
3
  push Temp             ; Jetzt SREG sichern
4
  .....
5
End_ISR_Prog:           ; mit Sprüngen auf diese Marke aussteigen
6
                         
7
  POP  Temp             ; das ist jetzt SREG
8
  Out  SREG, Temp       ; also SREG auf alten Stand
9
  POP  Temp             ; und jetzt Temp wieder herstellen

Auch ist es sinnvoll, immer nur einen Ausstieg aus einer Routine zu 
haben. Muß man mal ein weiteres Register innerhalb einer ISR verwenden, 
braucht es nur an einer Stelle zurückgepopt werden. Unterprogramme mit 
mehr als einem RET oder RETI sollte man sich gar nicht erst angewöhnen. 
Auch wenn es bei den Profis wohl "normal" ist.
Gruß oldmax

von Vuvuzelatus (Gast)


Lesenswert?

>Wenn du in einer ISR
>eine Anzeige multiplext, so wie ich es beschrieben habe und Eingänge per
>Interrupt abfragst bzw. bearbeitest, mußt du schon damit rechnen, das
>ein sehr kurzes Signal in der Timer-ISR unterschlagen wird, wenn du
>diese Interrupts nicht zuläßt. Dann brauchst du auch keine
>Signalerfassung mit Interrupt bearbeiten, wenn du dieses einfach >ignorierst.

Ehrlich gesagt habe ich nicht verstanden, was Du damit sagen willst. Ich 
will überhaupt nichts ignorieren und selbstverständlich sind alle 
benutzten Interrupts zu jeder Zeit zugelassen. Nur nicht als 
verschachtelte Interrupts. Auch dann gehen keine Interrupts verloren; 
es kann höchstens passieren, dass eine ISR minimal verzögert aufgerufen 
wird, nämlich dann, wenn erst eine andere ISR zuende abgearbeitet werden 
muss (oder eine critical section wenn's welche gibt).

>Wieviel mehr Takte benötigt es denn, ein gerettetes SREG
>auch über das Register auf den Stack zu schreiben... auch nur 4
>Taktzyklen, 2 für Push und 2 für POP. Bei 0,5 µSek ist das grad mal
>1/2000 der ISR- Aufrufzeit von 1 mSek.

4 Taktzyklen schneller, 4 Bytes Flash-Memory weniger und ein (was ich 
gerade in Assembler wichtig finde) kompakterer und verständlicherer Code 
- für jede ISR. Also wen das nicht überzeugt.

>Unterprogramme mit
>mehr als einem RET oder RETI sollte man sich gar nicht erst angewöhnen.

Da ist was Wahres dran.

von spess53 (Gast)


Lesenswert?

Hi

>4 Taktzyklen schneller, 4 Bytes Flash-Memory weniger und ein (was ich
>gerade in Assembler wichtig finde) kompakterer und verständlicherer Code
>- für jede ISR. Also wen das nicht überzeugt.

Klar. Du kannst das Einlesen von SREG auch ganz einsparen. Du zerstörst 
den Inhalt doch sowieso. Also noch mal kürzer und nochmal schneller. Und 
das Programm reagiert auch noch lustiger. Erfolg auf der ganzen Linie.

MfG Spess

von Karl H. (kbuchegg)


Lesenswert?

Vuvuzelatus schrieb:

> 4 Taktzyklen schneller, 4 Bytes Flash-Memory weniger und ein (was ich
> gerade in Assembler wichtig finde) kompakterer und verständlicherer Code
> - für jede ISR. Also wen das nicht überzeugt.

Dein Code ist absolut nicht zeitkritisch.

Lieber ein richtiges Programm, dass ein wenig (für den Benutzer 
unmerklich) langsamer ist, als ein schnelles Programm das falsch ist.

> Noch ein Nachtrag:
>interrupt0:
>          push temp             ; Das SREG in temp sichern.
>          in   temp, SREG
>
>          rcall start           ;Timer starten
>
>          out SREG, temp        ; Die Register SREG und temp wieder
>          pop temp              ; herstellen
>          reti
>
>
> start:
>
>         ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
>         out     TCCR1B, temp
>         ret

Das ist genau das was ich mit zu vielen UNterprogrammen aus keinem guten 
Grund meinte. Hättest du das so geschrieben ...
1
>interrupt0:
2
         push temp             ; Das SREG in temp sichern.
3
         in   temp, SREG       
4
          
5
         ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
6
         out     TCCR1B, temp
7
8
         out SREG, temp        ; Die Register SREG und temp wieder
9
         pop temp              ; herstellen
10
         reti

... dann hättest du gesehen, dass temp (und damit die Sicherung des 
SREG) innerhalb der ISR zerstört wird. Man kann alles übertreiben. Auch 
die Aufteilung in Unterprogramme und hin und her springen.

von Karl H. (kbuchegg)


Lesenswert?

Martin Vogel schrieb:

> @KHB
> Der Tipp mit den kleinen Unterprogrammen ist schon ernst gemeint.
>
> Wenn man eine Funktion zusammenfassen kann, sollte man es auch tun.
> Dadurch begrenzt sich auch die Fehlersuche. Und wenn man es richtig
> macht, behält man auch den Überblick.

Du sagst es, WENN man es richtig macht.

So
1
stoppuhr:
2
                                    ; Vergleichswert 
3
        ldi     temp, high( 8000 - 1 )
4
        out     OCR1AH, temp
5
        ldi     temp, low( 8000 - 1 )
6
        out     OCR1AL, temp
7
                                    
8
                                    
9
        ldi     temp, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
10
        out     TCCR1B, temp
11
 
12
        ldi     temp, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
13
        out     TIMSK, temp
14
        ret
15
16
17
18
multiplextimer:     ;für die multiplexfrequenz
19
20
        ldi     temp, ( 0 << CS01 ) | ( 0 << CS00 ) | ( 0 << CS02 )
21
        out     TCCR0, temp
22
 
23
        ldi     temp, 1 << TOIE0
24
        out     TIMSK, temp
25
        ret

ist es aber nicht richtig gemacht. Je nach Aufrufreihenfolge von 
stoppuhr und multiplextimer ist entweder der eine oder der andere 
Interrupt freigegeben. Aber niemals beide

(Nur so als Tip für den TO)

von Vuvuzelatus (Gast)


Lesenswert?

>Klar. Du kannst das Einlesen von SREG auch ganz einsparen.

Ich habe nirgendwo behauptet, dass man das Einlesen ganz einsparen kann. 
Also was soll das bitte?

Ich reserviere irgendein Register, z. B. 'SREGSave', das nur zum Sichern 
und Wiederherstellen des SREG in Interrupt-Service-Routinen dient. Dazu 
schreibe ich an den Anfang jeder ISR

    in SREGSave, SREG

und an das Ende, d. h. unmittelbar vor dem (einen) reti

    out SREG, SREGSave

Das ist alles. Ich habe nicht gesagt, dass man das so machen muss. Aber 
man kann es so machen. Es beeinträchtigt die Funktion des Programms in 
keiner Weise. Es macht nur alle ISRs etwas schneller, den Code etwas 
kürzer und (für mein Empfinden) etwas angenehmer lesbar. Das mag nicht 
viel sein, aber der Preis ist auch nicht hoch: Ein einziges Register. 
Das kann man doch gegeneinander abwägen.

von spess53 (Gast)


Lesenswert?

Hi

>>Klar. Du kannst das Einlesen von SREG auch ganz einsparen.

>Ich habe nirgendwo behauptet, dass man das Einlesen ganz einsparen kann.
>Also was soll das bitte?

Bitte vollständig zitieren:

>Klar. Du kannst das Einlesen von SREG auch ganz einsparen. Du zerstörst
>den Inhalt doch sowieso.

Und das entspricht deinem Programm.

>Ich reserviere irgendein Register, z. B. 'SREGSave', das nur zum Sichern
>und Wiederherstellen des SREG ...

Kenne ich. Widerspricht allerdings meiner Philosophie Register mit 
festgelegten Funktionen zu belegen. Und wenn ein Programm, wir deines, 
den Controller eigentlich nur Däumchen drehen lässt, was machen dann ein 
paar ns aus?

MfG Spess

von Vuvuzelatus (Gast)


Lesenswert?

>Und das entspricht deinem Programm.

Kann es sein, dass Du mich irrtümlich mit dem Threaderöffner 
identifizierst?

von spess53 (Gast)


Lesenswert?

Hi

>Kann es sein, dass Du mich irrtümlich mit dem Threaderöffner
>identifizierst?

Stimmt. Durch die ellenlangen Beiträge ist es hier etwas 
unübersichtlich.
Dann vergiss den ersten Teil.
Ändert aber nichts daran, das das .def-Gedödel nur bei kleineren 
Programmen sinnvoll ist.

MfG Spess

von Christian (Gast)


Angehängte Dateien:

Lesenswert?

Tschuldigung das ich mich eine Zeit lang nicht mehr gemeldet habe, ich 
war krank und konnte nicht antworten...

Karl Heinz Buchegger schrieb:
> Ich würde:
> [...]
> die Timer auch ständig durchlaufen lassen und nicht starten/stoppen.
> Auf die Art hast du eine ständige Anzeige, durch den stets laufenden
> Multiplex-Interrupt und damit auch automatisch eine Rückmeldung ob die
> Uhr gerade läuft oder nicht.

Habe ich jetzt auch so gemacht.

Karl Heinz Buchegger schrieb:
> Die Stoppuhr Funktionalität würde ich mit einer zusätzlichen Variable
> lösen, die einfach nur aussagt, ob die Uhr gerade laufen soll oder
> nicht.
> In den Lichtschrankeninterrupts wird diese Variable auf 0 oder 1 gesetzt
> und der Timer Interrupt der Uhr prüft einfach ob die Variable 1 ist und
> wenn nicht, dann zählt die Uhr im Timerinterrupt einfach nicht hoch.

Das habe ich auch gemacht, jedoch nitcht mit einer Variabelen, sondern 
mit einem Kontroll-Byte (wie von oldmax vorgeschlagen) das auch noch die 
Interrupts "steuert". Die Lichtschrnaken können zwar jederzeit einen 
Interrupts auslösen, aber durch das Kontrollbyte wird geregelt was dabei 
passieren soll. Am Anfang ist nur INT0 frei, falls man ausversehen 
Lichtschranke2 auslöst passiert nichts. Wenn INTO die Stoppuhr 
hochzählen lässt wird er gesperrt und INT1 freigegeben. Wird dieser 
ausgelöst wird der Timer gestoppt und INT0 und INT1 werden gesperrt, 
sodass die Uhr nicht mehr ausgelöst werden kann.

Karl Heinz Buchegger schrieb:
> In der Hauptschleife läuft ständig die Umsetzung der Zeitvariablen auf
> die Anzeigestellen und der Multiplex-Interrupt läuft auch einfach nur
> ständig durch.

In der Berechnung sichere ich vor jeder Berechnung nun die Werte, da um 
die Zehnerstelle zu bestimmen immer wieder 10 abgezogen werden und somit 
die Werte verändert werden. Dies hatte immer wieder dazu geführt das die 
Uhr nicht weiter als 10 Hundertstel zählen konnte. Direkt nach der 
Berechnung werden die alten Werte wiederhergestellt, und das Problem ist 
gelöst.
Beim Debuggen ist mir aufgefallen das die anderen Timer-Interrupts die 
Berechnung immer wieder durcheinander gebracht haben und deshalb werden 
jetzt während der Berechnung alle Interrupts gesperrt. Dies führt unter 
Umständen dazu das die Uhr nicht mehr ganz genau stoppt, aber im 
µSekunden bereicht ist mir das egal.

Karl Heinz Buchegger schrieb:
> Welche Möglichkeit hat eigentlich der Benutzer, die Stoppuhr wieder auf
> 0 zu stellen für die nächste Messung?

Auf der Olimex Platine ist ein Restetknopf der den µC resettet.
Diesen Schalter wollte ich zum rücksetzen verwenden.

Martin Vogel schrieb:
> Da im Bereich der mSek. möglichst schneller Code sein sollte, verwende
> ich da Register, die ausschließlich mSek. zählen. Danach nehme ich
> Variablen. Ob nun 1 mal in der Sekunde eine Variable zusätzlich geladen
> und wieder zurückgespeichert wird, ist so ziemlich egal. Die
> Programmzeit erhöht sich da lediglich um ca. 4 Taktzyklen. ( bei 8 MHz
> 0,5 µSek )

Das habe ich nicht so richtig verstanden. Was verstehst du unter 
schneller Code? Mein Timer erhöht nur ein Register vergleicht es mit 
einem Wert und dann kommt auch schon der reti.
Sollte ich evtl. den SubCount weglassen und gleich die Hundertstel 
zählen?


Die SREG sichere ich jetzt bei jedem Interrupt und es gibt auch nur noch 
jeweils ein reti für jeden Interrupt, das auch immer genommen wird.

Karl Heinz Buchegger schrieb:
> ist es aber nicht richtig gemacht. Je nach Aufrufreihenfolge von
> stoppuhr und multiplextimer ist entweder der eine oder der andere
> Interrupt freigegeben. Aber niemals beide
>
> (Nur so als Tip für den TO)

Danke, das hatte ich übersehen. Mir war nicht klar das TIMSK für alle 
Timer gilt. Habe es verbessert und nur werden beide Interrupts 
freigegeben


WICHTIG-WICHTIG-WICHTIG

Ich denke mein Code läuft soweit ganz in Ordnung, aber es gibt ein 
Problem mit der Ausgabe des Codemusters für die Anzeige.
Im Programm wird das Muster auf PortD ausgegeben. An meinem µC hängen 
aber die letzten beiden Anschlüsse an PortB, da PD2 und PD3 durch die 
Interrupts belegt sind. Mir ist klar das ich das Codemuster für die 
Segmente ändern muss, aber wie gebe ich die letzten beiden Stellen auf 
PortB aus?
Muss ich dies mit YH und Yl und einer neuen Codetabelle nur für PortB 
alles nocheinmal berechnen lassen oder gibt es einen einfacheren Weg?
Ich hoffe ich konnte das Problem so beschreiben das man versteht was 
gemeint ist.

Den Code packe ich in den Anhang.

Grüße
Christian

von Christian (Gast)


Angehängte Dateien:

Lesenswert?

Hab gerade noch einen kleinen Fehler in meinem Code entdeckt und 
berichtigt.
Im Anhang ist die aktuelle Version

Grüße
Jörn

von Martin V. (oldmax)


Lesenswert?

Hi
So langsam nähern wir uns ja einem laufenden Programm. Also, schneller 
Code heißt nix anderes, das man möglichst wenig Befehle setzt. Wenn ich 
eine Variable nehme, muß ich diese erst in ein Register laden und dann 
wieder zurückschreiben. Das sind 4 Taktzyklen mehr. Im Msek. Bereich 
möchte man aber sicherstellen, das die gesamte Programmbearbeitung 
innerhalb der ISR weit unter dieser mSek. bleibt, weil sonst das 
Hauptprogramm, welches durch eine ISR ja ständig unterbrochen werden 
kan, überhaupt nicht mehr zum Zuge kommt. Daher bin ich in den 
"schellen" Teilen einer ISR schon mal geizig, was Befehle betrifft und 
opfer da ein Register. Der Atmega hat ja auch genug.
Was deine Berechnung betrifft, warum nimmst du nicht den Teiler ? Er ist 
wesentlich effizienter, als deine Subtraktion.
Wenn du es nicht verstanden hast, versuch ich es nochnal zu erklären. 
Dein Denken basiert mathematisch im 10er Bereich. Du kennst Ziffern von 
0-9. Nun schreib mal eine Division zweier Zahlen auf, so wie du es 
gelernt hast. Die Einzelergebnisse können ebenfals den Wert von 0 bis 9 
annehmen.
Nun dek mal binär. Da hast du nur Ziffern von 0 und 1. Also kann ein 
Schrittergebnis auch nur 0 oder 1 sein. Auf dieser Basis arbeitet die 
Ganzzahl Division von KHB. Ich hab diese nur auf 16 Bit reduziert, weil 
es für meinen Bereich völlig ausreicht und dadurch natürlich auch 
schneller ist. Das liegt daran, das dieses Programm eine eigene Schleife 
hat und solange er in der Berechnung hängt, natürlich nichts im 
Hauptprogramm macht.
Du hast deine ISR aufgebaut. Du weißt, das du 1/10 Sek. messen willst, 
also in jeder 1/10 Sekunde erhöhst du deinen Zähler. Das geschieht in 
der ISR, klar. Nun setzt du einfach ein Bit, was deinem Hauptprogramm 
anzeigt, mein Zähler hat sich geändert.
Im Hauptprogramm fragst du dieses Bit ab und sollte es gesetzt sein, 
springst du in eine Routine "Berechne".
Hier berechnest du die Zahlen und setzt die Codes, die vom Multiplexer 
für die Anzeige gebraucht werden. Erinner dich, der Multiplexer wird im 
mSek.-Takt aus der ISR aufgerufen.
Also, was muß in der Routine "Berechne" durchgeführt werden ? Klar, 
deine 2Byte-Integer -Zahl muß erst mal durch 10 geteilt werden. Das 
ganzzahlige Ergebnis bekommst du zurück und den Rest. Dwer ist natürlich 
nur im Bereich von 0-9, also im Low-Byte der Rückgabewerte. Die Register 
habe ich ja schon entsprechend benannt:
Reg_AL für Low-Byte und Reg_AH für High-Byte.
Also liefert Reg_AH und Reg_AL die 16 Bitzahl, die duch die 16-Bitzahl 
in Reg_BH und Reg_BL geteilt wird. Das Ergebnis steht in Reg_AH und 
Reg_AL sowie der Rest in Reg_BL. Um nun in "Berechne" den aktuellen 
Zählerstand in eine Zahl für die anzeige zu wandeln, ist es ganz 
einfach. Zuerst die Zehntel-Sekunden
Also Zähler, / 10. Ergebnis sind Sekunden und Rest sind 1/10 Sekunden. 
Ergebnis nun durch 60 teilen. Ergebnis Minuten, Rest Sekunden. Da die 
Sekunden noch zweistellig sind, die Minuten merken und die Sekunden 
durch 10 teilen. Ergebnis dund Rest entsprechen den zwei 
Sekundenziffern.
Gleiches noch mit Minuten und fertig.
Hier mal ein wenig Code, damit du die Angst vor solchen Programmen 
verlierst:
1
Zeitzaehler:
2
   Push Reg_A             ; Aufruf aus ISR, daher Register sichern !
3
   LDS  Reg_A, Zaehler_L  ; niederwertigen Zähler laden
4
   INC  Reg_A
5
   STS  Zaehler_L         ; zurückschreiben
6
   BRNE End_Zaehler       ; Überlauf durch 255+1 ergibt 0
7
   LDS  Reg_A, Zaehler_H  ; dann das High-Byte incrementieren
8
   INC  Reg_A
9
   STS Zaehler_H, Reg_A   ; und zurückschreiben
10
End_Zaehler:              
11
   LDS  Reg_A, Counter_Ctrl
12
   ORI  Reg_A, 0b00000001 ; Flag für Anderung Zähler
13
   STS  Counter_Ctrl, Reg_A
14
   POP  Reg_A             ; und POP hinter die End-Marke !
15
RET
Den folgenden Programmblock rufst du aus deiner Programmschleife auf, 
wenn du erkennst, das das Bit in Counter_Ctrl gesetzt ist.
1
Berechne:                 
2
   LDS Reg_AH, Zaehler_H
3
   LDS Reg_AL, Zaehler_L
4
   CLR Reg_BH
5
   LDI Reg_BL,10
6
   RCALL Div_Word
7
   STS  Msek_Anz2, Reg_BL     ; für 1/100 Sekunden
8
   LDI Reg_BL,10
9
   RCALL Div_Word             ; in Reg_AH und Reg_AL ist ja das Ergebnis
10
   STS  Msek_Anz1, Reg_BL     ; für 1/10 Sekunden
11
   LDI Reg_BL,60
12
   RCALL Div_Word             ; Ergebnis Sekunden in Reg_AH und Reg_AL 
13
   STS  Reg_AH, Min_H         ; Minuten merken 
14
   STS  Reg_AL, Min_L         ; Minuten merken 
15
   Mov  Reg_AH, Reg_BH        ; Sekunden in Divident
16
   Mov  Reg_AL, Reg_BL
17
   CLR  Reg_BH
18
   LDI  Reg_BL, 10 
19
   RCALL Div_Word             ; Ergebnis in Reg_AL und Reg_BL (Sekunden)
20
   STS  Sek_Anz0, Reg_BL
21
   STS  Sek_Anz1,Reg_AL
22
   LDS  Reg_AH, Min_H
23
   LDS  Reg_AL, Min_L
24
   CLR  Reg_BH
25
   LDI  Reg_BL,10
26
   ...                        ; den Rest solltest du schon selbst finden
27
   RCALL Set_Code
28
   LDS  Reg_A, Counter_Ctrl
29
   ANDI Reg_A, 0b11111110     ; Flag zurücksetzen
30
   STS  Counter_Ctrl, Reg_A   
31
RET
Diese so ermittelten Zahlen in den Variablen setzt du jetzt in den Code 
für die Anzeigen um. Das dürfte nun aber wirklich kaum probleme 
bereiten.
Wichtig ist, das du verstehst, welche Routinen mit der ISR verheiratet 
werden müssen und welche von der Programmschleife bearbeitet werden.
Gruß oldmax

von Vuvuzelatus (Gast)


Lesenswert?

>Klar, deine 2Byte-Integer -Zahl muß erst mal durch 10 geteilt werden.

Ich frag mich echt, warum Du Christian unbedingt diese 
16-Bit-Ganzzahldivision andienen willst. Er hat doch gar kein Problem, 
zu dessen Lösung sie nötig wäre. Er muss nur für die drei Variablen 
'Hundertstel', 'Sekunde' und 'Minute' eine Binär-zu-BCD-Umwandlung 
durchführen. Alle drei Variablen sind ein Byte groß und liegen 
garantiert im Bereich 0...99. Unter dieser Voraussetzung lässt sich die 
Umwandlung mit acht Instruktionen erledigen ('ret' nicht mitgezählt):
1
; input : t (Byte im Bereich 0...99)
2
; output: r1, r0 (Einer in r0, Zehner in r1)
3
4
ByteBinToBCD:
5
    mov    r0, t
6
    clr    r1
7
    ldi    a, 10
8
9
ByteBinToBCDLoop:
10
    inc    r1
11
    sub    r0, a
12
    brsh   ByteBinToBCDLoop
13
14
    dec    r1
15
    add    r0, a
16
17
    ret

Maximale Laufzeit = 48 Taktzyklen. Dein 'Div_Word' braucht bestenfalls 
etwa 170 Takte. Versteh mich nicht falsch: Ich hab nix gegen diese 
Routine, aber ich sehe auch keinen Sinn darin, eine 16-Bit-Operation 
durchzuführen, wenn man nur 8-Bit-Variablen umwandeln will.

Da man nun die einzelnen Dezimalstellen in diesem Programm als solche 
nirgendwo braucht, sondern nur ihre Siebensegment-Muster, würde ich die 
Routine so erweitern, dass die Inhalte der Ergebnisregister r0 und r1 
gleich an Ort und Stelle durch die Siebensegment-Muster ersetzt werden:
1
ByteBinToSevenSegPattern:
2
    mov    r0, t
3
    clr    r1
4
    ldi    a, 10
5
6
ByteBinToSevenSegPatternLoop:
7
    inc    r1
8
    sub    r0, a
9
    brsh   ByteBinToSevenSegPatternLoop
10
11
    dec    r1
12
    add    r0, a
13
14
    ldi    ZL, Low (SRAM_SEVENSEGMENTPATTERN)
15
    ldi    ZH, High(SRAM_SEVENSEGMENTPATTERN)
16
    add    ZL, r0
17
    ld     r0, Z    
18
19
    ldi    ZL, Low (SRAM_SEVENSEGMENTPATTERN)
20
    ldi    ZH, High(SRAM_SEVENSEGMENTPATTERN)
21
    add    ZL, r1
22
    ld     r1, Z    
23
24
    ret

Die zehn Siebensegment-Muster müssen bei dieser Lösung im SRAM stehen. 
Von da sind sie etwas bequemer zu laden als aus dem Programmspeicher 
(Flash) mit der lpm-Instruktion. Ins SRAM bekommt man sie, indem man sie 
im Zuge der Programminitialisierung einmal dorthin schreibt.

Befreit von jeder Redundanz wird 'rechnung' dann schön kompakt:
1
  mov   t, Hundertstel
2
  rcall ByteBinToSevenSegPattern
3
  sts   SRAM_SEGCODE1, r0
4
  sts   SRAM_SEGCODE2, r1
5
6
  mov   t, Sekunde
7
  rcall ByteBinToSevenSegPattern
8
  sts   SRAM_SEGCODE3, r0
9
  sts   SRAM_SEGCODE4, r1
10
11
  mov   t, Minute
12
  rcall ByteBinToSevenSegPattern
13
  sts   SRAM_SEGCODE5, r0
14
  sts   SRAM_SEGCODE6, r1

@Christian: Wie Du siehst, kann man über das Z-Register indiziert auf 
das SRAM zugreifen (mit X und Y gehts ebenso). Versuch mal, Dir das auch 
in der ISR zunutze zu machen. Da steht nämlich auch noch viel mehr Code 
drin als nötig.

>Also, schneller Code heißt nix anderes, das man möglichst wenig Befehle setzt.

Ich weiß, was Du meinst, aber trotzdem ist hier Vorsicht angebracht. 
Manchmal ist nämlich auch gerade ein "großer" Code schneller als ein 
kleiner, z. B. beim Entrollen von Schleifen. Christians Anzeige-ISR
ist zwar lang, aber trotzdem nicht übermäßig langsam, weil immer nur ein 
relativ kleiner Teil (1/5) davon durchlaufen wird.

Auch kleine Codes können viel Zeit verschlingen, wenn mans drauf anlegt:
1
    clr   ZL
2
    clr   ZH
3
    sbiw  ZL, 1
4
    brne  PC-1

braucht gut 262000 Taktzyklen.

von Martin V. (oldmax)


Lesenswert?

Hi
Schön, aber viele Wege führen nach Rom... er könnte ja eigentlich schon 
in der Zeitzählung schon einzelne Ziffern eintragen, dann braucht nix 
berechnet zu werdern. Jeder halt wie er es mag. Aber ich finde es schon 
ok, wenn Christian hier verschiedene Kösungen angeboten werden. Welchen 
Weg er gehen wird, das wird letztlich in seiner Facharbeit stehen. Es 
soll nicht unser Code sein....
>Ich frag mich echt, warum Du Christian unbedingt diese
>16-Bit-Ganzzahldivision andienen willst.
 Frag dich nicht, auch dein Code ist eine Lösung. Es ist übrigends nicht 
meine Division, sondern ich wies bereits darauf hin, das es nur die 
Umsetzung einer 32Bit-Division auf 16 Bit-Division ist. Was glaubst du 
muß nun geändert werden, wenn er eine 8Bit-Division ableitet? Wie 
gesagt, es ist seine Facharbeit, und wir haben ihm denke ich, alle ein 
wenig Hilfe zukommen lassen. Daher braucht es keinen "Krieg" unter 
Programmierern, welche Lösung die bessere ist...
Gruß oldmax

von Christian (Gast)


Lesenswert?

Hi,

Danke für die Antworten.
Ehrlichgesagt finde ich beide Lösungen gut und mir wird jetzt auch klar 
das es ein langer Weg ist bis man mal richtig gut und effizient 
programmieren kann. In meinem Code gibt es noch sehr viele unnötige 
Codezeilen, aber ich bin erstmal froh wenn er läuft. Optimiert wird dann 
später :)
Endlich verstehe ich auch mal die 16-Bit Division die mir oldmax schon 
von Anfang an erklären wollte.


Ich will damit ja nicht nerven, aber ich habe immer noch das Problem mit 
den zwei Ports, auf denen ich das Codemuster ausgeben muss, das ich 
nicht gelöst bekomme.
Weil die Interrupts PD2 und PD3 belegen muss ich die Ausgabe über PortD 
und PortB gemeinsam machen.
Das passende Muster muss so aufgeteilt werden das die Stellen 
0/1/4/5/6/7 auf PortD landen und 2/3 auf PortB.
Hat jemand von euch eine Idee wie man das lösen kann.
Es wäre mir wichtig wenn ich das Programm mal auf dem Chip laufen lassen 
könnte um zu sehen ob ich alles richtig gebaut und gelötet habe. Leider 
geht das wegen den verschiedenen Ports nicht.
Ich wäre euch sehr dankbar wenn jemand dazu eine Lösung hätte.

Beste Grüeß
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

>0/1/4/5/6/7 auf PortD landen und 2/3 auf PortB.

Und wo dort? Mach mal eine genaue Zuordnung Datenbit/Portpin.

MfG Spess

von Christian (Gast)


Angehängte Dateien:

Lesenswert?

spess53 schrieb:
> Hi
>
>>0/1/4/5/6/7 auf PortD landen und 2/3 auf PortB.
>
> Und wo dort? Mach mal eine genaue Zuordnung Datenbit/Portpin.
>
> MfG Spess

Ok dann versuch ichs mal:

a =  PD0
b =  PD1
c =  PB0
d =  PB1
e =  PD4
f =  PD5
g =  PD7
dp = PD8  (Dezimalpunkt)

Ich habe LEDSegmente im gemeinsamer Anode, also müssen die Segmente die 
leuchten sollen auf 0 gesetzt werden.

11000000      0: a, b, c, d, e, f
11111001      1: b, c
10100100      2: a, b, d, e, g
10110000      3: a, b, c, d, g
10011001      4: b, c, f, g
10010010      5: a, c, d, f, g
10000010      6: a, c, d, e, f, g
11111000      7: a, b, c
10000000      8: a, b, c, d, e, f, g
10010000      9: a, b, c, d, f, g

Im Byte ist die Reihenfolge also:

dp/g/f/e/d/c/b/a

Ich hoffe das hast du gemeint mit Zuordnung...

Grüße
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

Ich interpretiere das mal so:

   dp   g    f     e     d    c    b    a

  PD7  PD6  PD5   PD4   PB1  PB0  PD1  PD0

Dann sollte es so gehen (nicht getestet):
1
  lds r16,segcodex
2
  lsr r16
3
  lsr r16
4
  andi r16,0b00111100
5
  lds r17,segcodex
6
  andi r17,0b00000011
7
  or r16,r17
8
  in r17,PortD
9
  andi r17,0b11000000
10
  or r17,r16
11
  out PortD,r17
12
13
  lds r16,segcodex
14
  lsr r16
15
  andi r16,0b00000011
16
  in r17,PortB
17
  andi r17,0b11111100
18
  or r17,r16
19
  out PortB,r17

MfG Spess

von Martin V. (oldmax)


Lesenswert?

Hi
Also, ich würde in meinem Programm grundsätzlich erst mal die Bits in 
einer Variablen haten. Was spricht dagegen. Mußt halt nur Laden und 
wieder ablegen, wie gesagt, ein paar Taktzyklen mehr, aber von den 
zeiten hab ich ja schon was geschrieben. Zur Ausgabe auf den Port rufst 
du ein eigenes Unterprogramm auf. Klar, hier zwar indirekt aus der ISR 
heraus, aber das hatten wir ja schon. Nun holst du die auszugebenden 
Bytes und verteilst diese auf die Ausgänge. Hilfreich ist folgende 
Vorgehensweise:

Bitmuster zurechtschieben
Port lesen
gesetzte Bits zuerst löschen ( Und Maskierung)
anschließend mit positionierten Bits verodern
und Port wieder beschreiben.
Nächsten Port....

Da du dies in einer eigenen Routine machst, kannst du dich hier völlig 
auslassen und deine Bits verteilen wie's beliebt. Änderungen werden 
ausschließlich hier bearbeitet. Ehrlich gesagt, für diese Hilfestellung 
brauche ich hier nicht die Zuordnung der Portbits, es sei denn, ich 
würde dir noch mehr Code zukommen lassen.
denk immer daran, wenn du etwas "zurechtbasteln" mußt und noch keinen 
Durchblick hast, nimm Variablen und stütz dein Programm darauf ab. Die 
Subroutine kannst du immer schreiben
1
;********************************************
2
;* Variable Seg_Pos : Bit 0 Segment 10^0    *
3
;*                    Bit 1 Segment 10^1    *
4
;*                    ... usw ....          *
5
;*          Segment: Bit 0 = LED a          *
6
;*                   Bit 1 = LED b          *
7
;*                   .... usw ....          *
8
;********************************************
9
My_UnKnown_Sub:
10
11
RET
Wenn du dann dein Programm ausfüllen willst, brauchst du nur diese 
kleine Routine bearbeiten. Kommentarzeilen helfen in diesem Fall, das du 
vor Augen hast, was reinkommt. Es fehlt noch die Kommentierung, welche 
Bits in welchen Port zugewiesen werden. Das macht dein Programm auch 
noch nach 10 Jahren lesbar.... und diese Kommentarzeilen kosten im µC 
keinen Speicher !
Gruß oldmax

von Christian (Gast)


Lesenswert?

Hi Spess,

leider funktioniert es nicht.

Auf meine Anzeigeroutine angewendet sieht es folgendermaßen aus.
1
anzeige1:
2
  push temp             ; erst temp sichern.
3
  in   temp, SREG 
4
  push Temp             ;dann SREG
5
  cpi zaehler, 1
6
  brsh anzeige2
7
  out PORTC, ausschalten
8
;**********************************
9
  lds temp, Seg_Code1
10
  lsr temp
11
  lsr temp
12
  andi temp,0b00111100
13
  lds temp1,Seg_Code1
14
  andi temp1,0b00000011
15
  or temp,temp1
16
  in temp1,PortD
17
  andi temp1,0b11000000
18
  or temp1,temp
19
  out PortD,temp1
20
21
  lds temp,Seg_Code1
22
  lsr temp
23
  andi temp,0b00000011
24
  in temp1,PortB
25
  andi temp1,0b11111100
26
  or temp1,temp
27
  out PortB,temp1
28
;*********************************
29
  inc schalter
30
  out PORTC, schalter
31
  inc zaehler
32
  rjmp anzeige_ende
33
  [...]
34
35
anzeige_ende:
36
  pop  Temp             ; das ist jetzt SREG
37
  out  SREG, Temp       ; also SREG auf alten Stand
38
  pop  Temp             ; und jetzt Temp wieder herstellen
39
  reti

Hab jetzt nur den Teil mit anzeige1 gepostet.
Bei anzeige2 bis anzeige5 sieht es genauso aus nur mit dem Unterschied 
das eben Seg_Code2 bis Seg_Code verwendet werden.
Zwischen den Sternen hatte vorher einfach nur

lds temp, Seg_Code1
out PortD, temp

gestanden.

Das Problem ist das bei deinem Programm auf PortB ebenfalls nichts 
ausgegeben wird.

Ich bin aber nicht in der Lage dein Programm zu verbessern, da ich 
keinen Schimmer habe was ich machen soll.
Ich bin auf eure Hilfe angewiesen.

Grüße
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

Ich schrieb:

>Ich interpretiere das mal so:
>   dp   g    f     e     d    c    b    a
>  PD7  PD6  PD5   PD4   PB1  PB0  PD1  PD0

Ist das richtig so?

MfG Spess

von Christian (Gast)


Lesenswert?

Martin Vogel schrieb:
> Also, ich würde in meinem Programm grundsätzlich erst mal die Bits in
> einer Variablen haten.

Welche Bits meinst du den hier? Die für das Codemuster der Segmente?

Martin Vogel schrieb:
> Hilfreich ist folgende Vorgehensweise:
>
> Bitmuster zurechtschieben
> Port lesen
> gesetzte Bits zuerst löschen ( Und Maskierung)
> anschließend mit positionierten Bits verodern
> und Port wieder beschreiben.

Mir ist klar das ich das Muster irgendwie um zwei Stellen verschieben 
muss, aber in welche Richtung?
Außerdem sind ja dann zwei andere Bits der Stelle die eigentlich auf 
PortB liegen sollten.
Wie meinst du das mit Port lesen? Ich will dort ja nur das Muster 
ausgeben.

Wenn ich ehrlich bin verstehe ich nicht wie ich das lösen soll.

Bei dem Prg_Ctrl Byte ist mir das mit der Maskierung noch klar, aber 
hier mit den ganzen "und" und "oder" blicke ich nicht mehr so ganz 
durch.

In diesem Fall kann ich mir noch nichteinmal im Kopf vorstellen wie das 
Unterprogramm arbeiten soll, vom schreiben des Codes einmal ganz 
abgesehen.

Tut mir Leid aber ich blicke hier einfach nicht mehr durch.

Grüße
Jörn Christian

von Christian (Gast)


Lesenswert?

spess53 schrieb:
> Hi
>
> Ich schrieb:
>
>>Ich interpretiere das mal so:
>>   dp   g    f     e     d    c    b    a
>>  PD7  PD6  PD5   PD4   PB1  PB0  PD1  PD0
>
> Ist das richtig so?
>
> MfG Spess

Ich habe gerade nocheinmal nachgeschaut und habe gesehen das ich einen 
Fehler gemacht hatte.

     dp      g      f      e     d     c     b     a
     PB1    PB0    PD7    PD6   PD5   PD4   PD1   PD0

So ist es jetzt richtig.

Grüße
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

Okay. dann probiere mal das:
1
  lds r18,segcodex     ; Für jede Stelle
2
  rcall abcd
3
  out PortD,r16
4
  out PortB,r17
5
6
7
; Das ganze als Unterprogramm
8
abcd:                          
9
  mov r16,r18
10
  lsl r16
11
  lsl r16
12
  andi r16,0b00111100
13
  mov r17,r18
14
  andi r17,0b00000011
15
  or r16,r17
16
  in r17,PortD
17
  andi r17,0b11000000
18
  or r16,r17
19
  push r16
20
21
  mov r16,r18
22
  swap r16
23
  lsr r16
24
  lsr r16
25
  andi r16,0b00000011
26
  in r17,PortB
27
  andi r17,0b11111100
28
  or r17,r16             ;Wert für PortB
29
  pop r16                ;Wert füt PortD
30
  ret

MfG Spess

von Martin V. (oldmax)


Lesenswert?

Hi
@ Spess   du geizt aber auch ganz schön mit Kommentaren...
@Christian
Also,nix für Ungut, aber ein paarmal hab ich's bereits geschrieben: "Es 
ist deine Facharbeit!"
So langsam solltest du auch mal etwas verstehen, Und Oder und andere 
logische Verknüpfungen sind das A & O einer Programmierung.
Spess hat es bereits geschrieben, wenn ich dir noch einmal erkläre, 
warum ich die Ports immer erst einlese, dann die Bits setze und dann 
wieder ausgebe, werd ich mich nicht an den vorgeschlagenen Code von 
Spess halten.
1
;***************************************************
2
; Selektieren und ansteuern von 7 Segmentanzeigen  *
3
;* Seg_Pos   Bit 0 -6 Selektion Segment            *
4
;* Matrix    Bit 0-7 LED a-f                       *
5
;* Port      PortD  Bit 0 = a                      *
6
;*                  Bit 1 = b                      *
7
;*                  Bit 4 = c                      *
8
;*                  Bit 5 = d                      *
9
;*                  Bit 6 = e                      *
10
;*                  Bit 7 = f                      *
11
;*           PortB  Bit 0 = g                      *
12
;***************************************************
13
Set_SegAnz:
14
   Push Reg_A
15
   Push Reg_B
16
   LDS Reg_A, Matrix    ; Holen der Zeichenmatrix
17
   POP Reg_A            ; Zwischenspeichern, wird nochmal gebraucht
18
   ANDI Reg_A, 0b01000000 ; Bit LED g ausmaskieren (0x000000)
19
   SWAP Reg_A             ; Bit g ist jetzt auf pos. Bit 2 (00000x00)
20
   ROR  Reg_A             ; Position Bit 1  (000000x0)
21
   ROR  Reg_A             ; Position Bit 0  (0000000x)
22
   ANDI Reg_A, 0b00000001 ; nur die 1 auf bit 0 stehen lassen
23
   In   Reg_B, PInB       ; Port lesen, da nur Bit 0 verändert werden soll
24
   ANDI Reg_B, 0b11111110 ; Bit 0 löschen, da x auch eine 0 ergeben kann
25
   Or   Reg_B, Reg_A      ; Reg_Bmit Reg_A verodern (xxxxxxx0 mit 0000000x)
26
   Out  PortB, Reg_B
27
   POP Reg_A
28
   ANDI Reg_A, 0b00111111 ; Jetzt die LED a-f behandeln
29
......
30
31
   POP Reg_B
32
   POP Reg_A
33
RET
So, den Anfang hab ich dir gezeigt. Bei der Ausgabe auf port D mußt du 
ab Bit 2 mit ROL die Bits ab Bit 2 nach links schieben. NA ja, da ist 
auch wieder ausmaskieren zwischenspeichern und zusammenfügen angesagt. 
Nimm dir ein Blatt Papier und schreib dir auf, wie du es lösen willst. 
Wenn ich jetzt noch mehr schreibe, kannst du gleich alles 
zusammenkopieren und als Facharbeit abgeben....
Gruß oldmax

von Vuvuzelatus (Gast)


Lesenswert?

Ich würds schlicht Bit für Bit mit bst und bld zusammenpfriemeln. Viel 
kürzer wirds mit ner wilden AND-OR-ROR-Orgie auch nicht.
1
  ; t enthält das Siebensegmentmuster
2
  bst t, 0
3
  bld pod, PD0
4
  bst t, 1
5
  bld pod, PD1
6
  bst t, 2
7
  bld pod, PD4
8
  bst t, 3
9
  bld pod, PD5
10
  bst t, 4
11
  bld pod, PD6
12
  bst t, 5
13
  bld pod, PD7
14
  bst t, 6
15
  bld pob, PB0
16
  bst t, 7
17
  bld pob, PB1
18
19
  out PORTB, pob
20
  out PORTC, poc
21
  out PORTD, pod

pob, poc und pod sind fest während der gesamten Programmlaufzeit den 
Ports zugeordnete Register.

>er könnte ja eigentlich schon in der Zeitzählung schon einzelne Ziffern
>eintragen, dann braucht nix berechnet zu werdern.

Gut erkannt :-) Sofern man damit vom "ästhetischen Aspekt" her kein 
Problem hat, spräche tatsächlich nichts dagegen, es so zu machen.

von spess53 (Gast)


Lesenswert?

Hi

>@ Spess   du geizt aber auch ganz schön mit Kommentaren...

Soll doch heutzutage geil sein.

MfG Spess

von spess53 (Gast)


Lesenswert?

Hi

Irgendwie hatte ich gestern bezüglich der Segmentzuordnung einen Knoten 
im Hirn.
Hab das nochmal überarbeitet. Das Ganze geht von folgender Zuordnung 
aus:

    PD7   PD6   PD5   PD4   PD3   PD2   PD1   PD0
     f     e     d     c     X     X     b     a

    PB7   PB6   PB5   PB4   PB3   PB2   PB1   PB0
     X     X     X     X     X     X    dp     g

Kürzer ist es auch geworden.
1
  .....
2
  lds r18,segcodex     ; Aufruf
3
  rcall abcd           ; für jede Stelle
4
  out PortD,r16
5
  out PortB,r17
6
  ....
7
8
abcd:
9
  mov r16,r18          ; r16 = dp,gfedcba
10
  andi r16,0b00000011  ; r16 = 000000ba
11
12
  clr r17
13
  lsl r18              ;    r17          r18
14
  rol r17              ; 0000000,dp    gfedcba0
15
  lsl r18
16
  rol r17              ; 000000,dp,g   fedcba00
17
  andi r18,0b11110000  ; r18 = fedc0000
18
  or r16,r18           ; r16 = fedc00ba
19
20
; Mit den Ports verknüpfen
21
  in r18,PortD
22
  andi r18,0b00001100
23
  or r16,r18           ; Wert für PortD
24
25
  in r18,PortB
26
  andi r18,0b11111100
27
  or r17,r18           ; Wert für PortB
28
29
  ret

Falls Register gesichert werden müssen sind die 'push/pop' zu ergänzen.

MfG Spess

von Christian (Gast)


Lesenswert?

Hi

vielen Dank Spess.
Die Anzeige läuft jetzt wunderbar.
Hab das Programm mal auf den µC geladen und es hat alles super 
funktioniert.

Nur bei den externen Interrupts scheint noch irgendetwas nicht so 
richtig zu stimmen. Egal wie ich die Bits im Register MCUCR setze, INT0 
wird nur ausgelöst wenn ich Masse an den Pin halte und INT1 wird nur 
ausgelöst wenn ich 5V daran halte.
Das ändert sich auch nicht wenn ich die Bits in MCUCR auf steigende, 
falllende Flanke oder jede Änderung setze.
Egal bei welcher Einstellung INT0 startet die Uhr nur wenn ich Masse an 
den Pin halte und INT1 stoppt sie nur bei 5V.
Halte ich 5V an INT0 oder Masse an INT1 passiert garnichts.

Wenn die Uhr läuft funktioniert die Anzeige wunderbar nur mit den 
Interrupts stimmt etwas nicht.

Hat jemand eine Ahnung woran das liegen könnte?

Beste Grüße
Jörn Chrisitan

von spess53 (Gast)


Lesenswert?

Hi

>Halte ich 5V an INT0 oder Masse an INT1 passiert garnichts.

Wie jetzt? An die blanken Pins?

Wie sieht denn deine Beschaltung aus?

MfG Spess

von Christian (Gast)


Angehängte Dateien:

Lesenswert?

Hi

> Wie jetzt? An die blanken Pins?
>
> Wie sieht denn deine Beschaltung aus?

Ich hab mal ein Bild angehängt.

Die Kabel die von der Lichtschranke kommen belegen entweder die Stifte 
1-2 oder Stifte 2-3.

Stift 2 hat keinen Kontakt zu irgendeinem Bauteil, er dient nur zur 
Stabilisierung.

Stift 3 ist mit einem 330 Ohm Widerstand und einer LED nach Masse 
verbunden. Wenn man 2-3 belegt kann man testen ob die Lichtschranke 
überhaupt auslöst. Wenn sie unterbrochen wird leuchtet also die LED.

Im normalen Betrieb wird das Kabel an 1-2 angeschlossen und liegt somit 
dirket am µC-Pin an.

Testweise habe ich mir zwei Kabel direkt an 5V und an Masse 
angeschlossen und diese an Stift 1 gehalten.

An INT0 hat Masse den Interrupt ausgelöst und an INT1 waren es 5V.
INT1 funktioniert also mit der Lichtschranke, da diese bei 
Unterbrechnung auf High schaltet.
Nur bei INT0 ist es komischerweise Masse die den Interrupt auslöst.

Wenn man das nicht durch Programmieren lösen kann werde ich wohl einfach 
meine Lichtschranke umbauen, sodass sie geschlossen 5V abgibt und bei 
Unterbrechung 0V.

Grüße
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

Mach mal ein paar Pull-Up-Widerstände (10k) an die Eingänge.

MfG Spess

von Martin V. (oldmax)


Lesenswert?

Hi
Ja, bei dieser Beschaltung brauchst du Pull-Down, also ca.10K nach GND 
und die internen Pullup bitte abschalten, falls du sie irgendwo gesetzt 
hast.
Gruß oldmax

von Jörn Christian (Gast)


Angehängte Dateien:

Lesenswert?

Hab das hier zu Pull-Up/Down gefunden:
>Will man dafür sorgen, dass der Eingangspin logisch LOW erhält wenn die
>Taste gedrückt wird, so gilt das Schaltbild auf der linken Seite. Der
>Taster - es kann selbstverständlich auch ein Schalter sein - liegt
>zwischen dem Eingang des Gatters und GND. Der Pullup-Widerstand liegt
>zwischen dem Eingang und +Ub. Beim Öffnen des Tasters zieht der Pullup
>Widerstand die Spannung am Anschlusspin hoch bis zum
>Betriebsspannungswert +Ub, was logisch HIGH entspricht. Will man dafür
>sorgen, dass der Eingangspin logisch HIGH erhält wenn die Taste gedrückt
>wird, so gilt das Schaltbild auf der rechten Seite. Der Kontakt liegt
>zwischen dem Eingang des Gatters und +Ub. Der Pulldown-Widerstand liegt
>zwischen dem Eingang und GND. Beim Öffnen des Kontaktes zieht der
>Pulldown-Widerstand die Spannung am Eingang hinunter auf GND, was
>logisch LOW entspricht. LOW oder HIGH wird am Eingang nur dann per
>Widerstand erreicht, wenn es ein CMOS-Eingang ist, weil dieser extrem
>hochohmig ist.

spess53 schrieb:
> Hi
>
> Mach mal ein paar Pull-Up-Widerstände (10k) an die Eingänge.

Martin Vogel schrieb:
> Hi
> Ja, bei dieser Beschaltung brauchst du Pull-Down, also ca.10K nach GND
> und die internen Pullup bitte abschalten, falls du sie irgendwo gesetzt
> hast.

Interne Pullups hab ich nirgends gesetzt, außer vll unbewusst. Ich schau 
aber nochmal drüber. PullUps setzen kann man ja nur wenn man auf einem 
als Eingang geschalteten Port eine 1 ausgibt. Da die Codesegmente ja 
über PortB und PortD (außer die beiden Interrupts) ausgegeben werden 
gibt es keine Möglichkeit irgendetwas in PD2 und PD3 zu schreiben und 
somit die PullUps zu aktivieren.

Soll ich jetzt Pull-Up oder Pull-Down Widerstand verwenden?
Laut der Beschreibung müssten es Pull-Downs sein.

Wenn ich das richtig verstanden habe kann ich mir den Schalter als 
Eingang meines Signals vorstellen, da wenn die Lichtschranke geschlossen 
ist 0V fließen(Schalter auf) und wenn sie unterbrochen wird 5V(Schalter 
geschlossen).

Somit könnte ich im Schaltbild einfach den Schalter gegen den Anschluss 
für mein Lichtschranken Signal tauschen.
Sehe ich das soweit richtig oder habe ich mir das falsch überlegt?

Grüße
Jörn Christian

von spess53 (Gast)


Lesenswert?

Hi

>Soll ich jetzt Pull-Up oder Pull-Down Widerstand verwenden?
>Laut der Beschreibung müssten es Pull-Downs sein.

Pull-Down. Wollte ich eigentlich auch schreiben. Pull-Up-Widerstände 
sind hier sinnlos.

MfG Spess

von Jörn Christian (Gast)


Lesenswert?

Kann ich dann anstelle des Tasters im Schaltbild einfach mein 
Signalkabel von der Lichtschranke anschließen?

Das Signal hat ja nur 0V und 5V und ist somit ein Taster mit aus und an.

Oder habe ich dort einen Denkfehler drin?

Grüße
Jörn Christian

von spess53 (Gast)


Angehängte Dateien:

Lesenswert?

Hi

>Das Signal hat ja nur 0V und 5V und ist somit ein Taster mit aus und an.

Ohne Pull-Down-Widerstand hast du nur einen definierten Zustand: ca. 5V 
wenn der Ausgangstransistor durchgeschaltet ist. Bei gesperrtem 
Ausgangstransistor wirst du mit einem hochohmigen Messgerät alles 
mögliche messen, nur keine 0V. Und der AVR sieht das genauso.

Dein Programm von
Beitrag "Re: Probleme bei Programmierung von Lichtschranken-Stoppuhr"
habe ich mal etwas eingedampft:
- SubCount ist unnötig. Mit Vorteiler 8 und OCR1A=9999 wird der 
Interrupt
  alle 1/100 s aufgerufen.
- wenn du auch 1/10 und 10er s benutzt sparst du das Dividieren
- 'Rechnung' als Schleife
- 'Anzeige1' ohne Spagetti-Code

Kannst es dir ja mal ansehen.

MfG Spess

von oldmax (Gast)


Lesenswert?

Hi

>Somit könnte ich im Schaltbild einfach den Schalter gegen den Anschluss
>für mein Lichtschranken Signal tauschen.
>Sehe ich das soweit richtig oder habe ich mir das falsch überlegt?

Ja, du kannst den Schalter durchden Ausgangstransistor, bzw.durch deine 
Schaltung ersetzen und mußt einen PULL-DOWN Widerstand (ca.10 K) 
einsetzen.
Gruß oldmax

von Jörn Christian (Gast)


Angehängte Dateien:

Lesenswert?

oldmax schrieb:
> Hi
>
>>Somit könnte ich im Schaltbild einfach den Schalter gegen den Anschluss
>>für mein Lichtschranken Signal tauschen.
>>Sehe ich das soweit richtig oder habe ich mir das falsch überlegt?
>
> Ja, du kannst den Schalter durchden Ausgangstransistor, bzw.durch deine
> Schaltung ersetzen und mußt einen PULL-DOWN Widerstand (ca.10 K)
> einsetzen.
> Gruß oldmax

Hi,

leider hat das nicht wirklich geklappt...
Ich hab den Schalter jetzt durch nen Transistor ersetzt der durch das 
LichtschrankenSignal angesteuert wird.
Bei INT1 hab ich eine Pulldown-Schaltung gebaut und somit wird dort eine 
steigende Flanke geliefert.
Bei INT0 habe ich eine Pullup-Schaltung verwendet und somit bekommt der 
Pin Low wenn die Lichtschranke ausgelöst wird.
Ich weiß nicht warum es nicht bei beiden mit Pulldown funktioniert hat, 
aber so läuft es jetzt.

Danke für den optimierten Code Spess.
Der Anzeige- und Berechnungs-Teil sind um Welten übersichtlicher als bei 
mir. Der SubCount ist auch unnötig da man ja gleich Hundertstel zählen 
kann.

Hab euch mal ein Bild vom provisorischen Aufbau angehängt.

Vielen Dank nochmal an alle, und vorallem an oldmax, spess und 
vuvuzelatus, die mir beim Programmieren zu Seite gestanden haben, ohne 
euch hätte ich das sehr wahrscheinlich nicht geschafft.
Jetzt mach ich mich erst einmal daran die 13-15 Seiten zu schreiben.
Am 15.04 hab ich Abgabetermin und wenn es jemanden interessiert kann ich 
ihm die Facharbeit auch dann mal schicken.

Beste Grüße
Jörn Christian

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.