mikrocontroller.net

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


Autor: Christian (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Der Code nochmal in einer besseren Version.
.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 -> 10 Tausendstel = 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 bis auf PD2 und PD3 ist 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 allgemein 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

Autor: philipp (Gast)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: oldmax (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
         LDS      Reg_A, Wert_1
         LDI      Reg_B, 0
         LDI  XL,LOW(Matrix_0)  ; x-Pointer 1.Matrixwert
  LDI  XH,HIGH(Matrix_0)
  ADD  XL, Reg_A
  ADC  XH, Reg_B    ; zur Matrixadresse addieren
         LD       Reg_A, x
         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

Autor: Christian (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht 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:
>
>       LDS      Reg_A, Wert_1
>       LDI      Reg_B, 0
>       LDI  XL,LOW(Matrix_0)  ; x-Pointer 1.Matrixwert
>   LDI  XH,HIGH(Matrix_0)
>   ADD  XL, Reg_A
>   ADC  XH, Reg_B    ; zur Matrixadresse addieren
>       LD       Reg_A, x
>       STS      Seg_Code_1,Reg_A
>

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
.include "m8def.inc"

.def zero       = r1
.def h_Einer    = r2
.def h_zehner   = r3
.def sek_Einer  = r4
.def sek_Zehner = r5
.def min_Einer  = r6
.def temp  = r16
.def temp1 = r17
.def temp2 = r18
.def temp3 = r19
.def Flag  = r20
 
.def SubCount = r21           
.def Hundert  = r22
.def Sekunden = r23
.def Minuten  = r24
.def test     = r25
 
.org 0x000
         rjmp main            ; Reset Handler
.org INT0addr
         rjmp int0    ; Ext. Interrupt0 Handler
.org INT1addr
         rjmp int1    ; 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 intialisieren
    out SPH, temp
        
    rcall ausgang 
    
    rcall interrupt

    rcall stoppuhr

    rcall frequenztimer
                                      
    rcall clr_uhr

    sei                         ; Interrupts allgemein aktivieren


loop:   rjmp loop

ausgang:
    ldi temp, 0b11110011        ;Port D bis auf PD2 und PD3 ist 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
        ret

interrupt:

        ldi temp, 0b00001111 ; INT0 und INT1  auf steigende Flanke konfigurieren   
        out MCUCR, temp

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

stoppuhr:
                                    ; Vergleichswert 
        ldi     temp1, high( 8000 - 1 )
        out     OCR1AH, temp1
        ldi     temp1, low( 8000 - 1 )
        out     OCR1AL, temp1
                                    
                                    
        ldi     temp1, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
        out     TCCR1B, temp1
 
        ldi     temp1, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
        out     TIMSK, temp1
        ret

frequenztimer:     ;für die multiplexfrequenz

        ldi     temp, ( 1 << CS01 ) | ( 1 << CS00 )
        out     TCCR0, temp
 
        ldi     temp, 1 << TOIE0
        out     TIMSK, temp
        ret
clr_Uhr:

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

        ldi     temp1, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
        out     TCCR1B, temp1
        ret
stopp:

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

ziffer_berechnen:

;** Zehner  **

         ldi   sek_Zehner, '0'-1  ; sek_Zehner mit '0'-1 vorladen
zehner:
         inc   sek_Zehner         ; erhöhen (somit ist nach dem ersten
                                  ; Durchlauf eine '0' in sek_Zehner)
         subi  Sekunde, 10        ; 10 abziehen
         brcc  zehner             ; ist dadurch kein Unterlauf enstanden?
                                  ; nein, dann zurück zu zehner
         subi  Sekunde, -10       ; 10 wieder dazuzählen, da die
                                  ; vorherhgehende Schleife 10 zuviel
                                  ; abgezogen hat
;** Einer **

         ldi   sek_Einer, '0'-1   ; sek_Einer mit '0'-1 vorladen
einer:
         inc   sek_Einer          ; erhöhen (somit ist nach dem ersten
                                  ; Durchlauf eine '0' in sek_Einer)
         subi  Sekunde, 1         ; 1 abziehen
         brcc  zehner             ; ist dadurch kein Unterlauf enstanden?
                                  ; nein, dann zurück zu einer
         subi  Sekunde, -1        ; 1 wieder dazuzählen, da die
                                  ; vorherhgehende Schleife 1 zuviel
                                  ; abgezogen hat

code_sek_einer_ausgeben:

         lds      temp, sek_Einer
         ldi      temp1, 0
         ldi  XL,LOW(Codes*2)  ; x-Pointer 1.Matrixwert
         ldi  XH,HIGH(Codes*2)
         add  XL, temp
         adc  XH, temp1    ; zur Matrixadresse addieren
         ld       temp, X
         sts      Seg_Code_3,temp

;****** das war nur ein Beispiel an der Einerstelle der Sekunde um zu sehen ob diese Idee richtig ist?******





multiplex:
; hier weiß ich nicht wie ich den richtigen LED-Baustein ansteuere und das passende Muster auf ihm ausgebe
         out PORTD , Seg_Code
;hier fehlt noch einiges
         reti      ; am schluss wieder zu der loop endloss-schleife springen 
int0:
         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
 
int1:
         push temp             ; Das SREG in temp sichern. 
         in   temp, SREG       
 
         rcall stopp           ;Timer stoppen
 
         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    end_isr             ; 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    end_isr             ; 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    end_isr             ; wieder zurücksetzen -> Minuten nur ein Segment, größte Zahl ist 9
 
                                    ; Überlauf von Minuten
        clr     Minuten             ; Minuten rücksetzen
        rjmp    loop

end_isr:
 
        out     sreg,temp1          ; sreg wieder herstellen
        pop     temp1
        rjmp    loop                ;


Codes:                               ; Die Codetabelle für die Ziffern 0 bis 9
                                     ; sie regelt, welche Segmente für eine bestimmte
                                     ; Ziffer eingeschaltet werden müssen
                                     ;KANN JEDOCH WEGEN PORTB NICHT VERWENDET WERDEN
           .db     0b11000000        ; 0: a, b, c, d, e, f
           .db     0b11111001        ; 1: b, c
           .db     0b10100100        ; 2: a, b, d, e, g
           .db     0b10110000        ; 3: a, b, c, d, g
           .db     0b10011001        ; 4: b, c, f, g
           .db     0b10010010        ; 5: a, c, d, f, g
           .db     0b10000010        ; 6: a, c, d, e, f, g
           .db     0b11111000        ; 7: a, b, c
           .db     0b10000000        ; 8: a, b, c, d, e, f, g
           .db     0b10010000        ; 9: a, b, c, d, f, g 


.DSEG
Seg_Code1:    .byte 1         ; Ausgabemuster für Segment 1
Seg_Code2:    .byte 1         ; Ausgabemuster für Segment 2
Seg_Code3:    .byte 1         ; Ausgabemuster für Segment 3
Seg_Code4:    .byte 1         ; Ausgabemuster für Segment 4
Seg_Code5:    .byte 1         ; Ausgabemuster für Segment 5

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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.
;zuerst mal Register definieren
.Def    mSek_0  = r1    ; Zählregister 1/1000 in ISR
.Def    mSek_1  = r2    ; Zählregister für 1/100 Sekunden in ISR
.Def    msel_2  = r3    ; Zählregister für 1/10 Sekunden
.Def    Dekade  = r4    ; Register für Vergleich auf 10 in ISR
.def  Reg_AL  = r16
.def  Reg_AH  = r17
.def  Reg_BL  = r18
.def  Reg_BH  = r19

.def  Reg_CL  = r20
.def  Reg_CH  = r21
.Def    Reg:Cnt = r22

;-------------- Division 16 ---------------------

;********************************************************
;* Vorzeichenlose 16-Bit-Division               *
;* Reg_AH, Reg_AL = Reg_AH, Reg_AL / Reg_BH, Reg_BL  *
;* Reg_BH, Reg_BL = Rest        *
;******************************************************** 

Div_Word:
  clr Reg_CL   ; Hilfsregister
  clr Reg_CH
  ldi Reg_Cnt, 16  ; Schrittregister
div_Loop1: 
  LSL Reg_AL
  rol Reg_AH

  rol Reg_CL
  rol Reg_CH
  
  cp Reg_CL, Reg_BL
  cpc Reg_CH, Reg_BH

  brcs div_Loop2
  Sub Reg_CL, Reg_BL
  SBC Reg_CH, Reg_BH

  inc Reg_AL
div_Loop2: 
  dec Reg_Cnt
  brne div_Loop1
  mov Reg_BL, Reg_CL
  mov Reg_BH, Reg_CH

ret
;**********************************************
Nun vereinbarst du für jede darzustellende Ziffer eine Variable sowie 
eine für den entsprechenden Segmentcode.
Also
; die kleinen Werte in Register packen, da schnellerer Zugriff. 
; in ISR ist dies wichtig. Bei den gößeren Werten spielt es keine 
; Rolle mehr. Daher können hier auch Variablen benutzt werden.
Sekunde:     .Byte 1     ; erklärt sich selbst
Minute:      .Byte 1
MSek_Anz1:   .Byte 1     ; für 1/100 Sekunden
Msel_Anz2:   .Byte 1     ; für 1/10 Sekunden
Sek_Anz0:    .Byte 1     ; erklärt sich selbst
Sek_Anz1:    .Byte 1
Min_Anz0:    .Byte 1
Min_Anz1:    .Byte 1
; Darstellbereich mit 16 Bitzahl 65535 ergibt rd. 10 Minuten Stopzeit
; analog dazu Variablen für den Segmentcode
MSek_Code1:   .Byte 1     ; für 1/100 Sekunden
Msel_Code2:   .Byte 1     ; für 1/10 Sekunden
Sek_Code0:    .Byte 1     ; erklärt sich selbst
Sek_Code1:    .Byte 1
Min_Code0:    .Byte 1
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

Autor: Christian (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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 :)
.include "m8def.inc"

.def null        = r1
.def ausschalten = r2
.def temp        = r16
.def Zehner      = r17
.def zaehler     = r18
.def flag        = r19
.def SubCount    = r20           
.def Hundertstel = r21
.def Sekunden    = r22
.def Minuten     = r23
.def schalter    = r24

 
.org 0x000
         rjmp main             ; Reset Handler
.org INT0addr
         rjmp interrupt0       ; Ext. Interrupt0 Handler
.org INT1addr
         rjmp interrupt1       ; Ext. Interrupt1 Handler
.org OC1Aaddr
           rjmp timer1_compare ;Timer1 Handler
.org OVF0addr
           rjmp anzeige1        ;Timer0 Multiplexfunktion Handler

 
main:  ; Hauptprogramm
    ldi temp, LOW(RAMEND)
    out SPL, temp
    ldi temp, HIGH(RAMEND) ;Stackpointer intialisieren
    out SPH, temp
        
    rcall InOut            ;IO-Register einstellen
    rcall interrupts       ;Interrupts einstellen
    rcall stoppuhr         ;Stoppuhr(Timer1) einstellen
    rcall multiplextimer   ;Multiplexfrequenz(Timer0) einstellen                                  
    rcall clear            ;Stoppuhr auf Null stellen und Variablen auf null setzen
    sei                    ; Interrupts allgemein aktivieren

loop:   
    cpi flag,1
    breq berechnung      ;bei gesetzter 1 vom Stopp-interrupt berechnung starten
    cpi flag,2
    breq multiplex       ; bei gesetzter 2 von berechnungs routine anzeige starten
    rjmp loop            ; ansonsten Endlosschleife


;************************************UNTERPROGRAMME*********************************************
;***********************************************************************************************

multiplex:
    ldi  temp, ( 1 << CS01 ) | ( 1 << CS00 )  ;Timer/Frequenz aktivieren
    out  TCCR0, temp
    clr flag
    sei
    rjmp loop

;***********Steuerung der LED-Segmente und Anzeigen des im SRAM abgelegten Musters**********
anzeige1:   
    cpi zaehler, 1           ;vergleichen zähler mit 1
    brsh anzeige2            ;größer oder gleich 1 -> anzeige2
    out PORTC, ausschalten   ;ansonsten segemt ausschalten
    lds temp, Seg_Code1
    out PORTD, temp          ;muster für zahl ausgeben
    inc schalter             
    out PORTC, schalter      ;Segment einschalten
    inc zaehler              ;zähler erhöhen
    reti
anzeige2:   
    cpi zaehler, 2
    brsh anzeige3
    out PORTC, ausschalten
    lds temp, Seg_Code2
    out PORTD, temp
    lsl schalter
    out PORTC, schalter
    inc zaehler
    reti
anzeige3:    
    cpi zaehler, 3
    brsh anzeige4    
    out PORTC, ausschalten
    lds temp, Seg_Code3
    out PORTD ,temp
    lsl schalter
    out PORTC, schalter
    inc zaehler
    reti
anzeige4:     
    cpi zaehler, 4
    brsh anzeige5    
    out PORTC, ausschalten
    lds temp, Seg_Code4
    out PORTD ,temp
    lsl schalter
    out PORTC, schalter
    inc zaehler
    reti
anzeige5:      
    out PORTC, ausschalten
    lds temp, Seg_Code5
    out PORTD ,temp
    lsl schalter
    out PORTC, schalter
    clr zaehler
    clr schalter
    reti
;*****************Berechnung der Anzeigecodes für die Segmente******************************
berechnung:
    cli
    cpi Hundertstel, 10  ;Divident > 10 ?
    brlo ausgabe         ; ja -> nur Einer vorhanden, weiter zu ausgabe
    subi Hundertstel,10  ; nein: 10 abziehen
    inc Zehner           ; Zehner +1
    rjmp berechnung      ; solange bis Divident < 10 dann weiter mit Einer
ausgabe:
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
    ldi ZH,HIGH(Codes*2)
    add ZL, Zehner        ;Abstand Zehner dazu
    adc ZH, null          ; + Übertrag 16bit-Operation
    lpm                   
    sts Seg_Code2, r0     ;Code im SRAM ablegen
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
    ldi ZH,HIGH(Codes*2)
    add ZL, Hundertstel   ;Abstand Einer dazu
    adc ZH, null          ; + Übertrag 16bit-Operation
    lpm
    sts Seg_Code1 , r0    ;Code im Sram ablegen
    clr Zehner
berechnung1:
    cpi Sekunden, 10      ;Divident > 10 ?
    brlo ausgabe1         ; ja -> nur Einer vorhanden, weiter zu ausgabe
    subi Sekunden,10      ; nein: 10 abziehen
    inc Zehner            ; Zehner +1
    rjmp berechnung1      ; solange bis Divident < 10 dann weiter mit Einer
ausgabe1:
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
    ldi ZH,HIGH(Codes*2)
    add ZL, Zehner        ;Abstand Zehner dazu
    adc ZH, null          ; + Übertrag 16bit-Operation
    lpm                   
    sts Seg_Code4, r0     ;Code im SRAM ablegen
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
    ldi ZH,HIGH(Codes*2)
    add ZL, Sekunden      ;Abstand Einer dazu
    adc ZH, null          ; + Übertrag 16bit-Operation
    lpm
    sts Seg_Code3 , r0    ;Code im Sram ablegen
    clr Zehner
berechnung2:    
    ldi ZL,LOW(Codes*2)   ;Z = Anfangsadresse Tabelle
    ldi ZH,HIGH(Codes*2)
    add ZL, Minuten       ;Abstand Einer dazu, da mit mehr als 9 Minuten möglich sind
    adc ZH, null          ; + Übertrag 16bit-Operation
    lpm
    sts Seg_Code5 , r0    ;Code im Sram ablegen
    ldi flag,2
    rjmp loop

;**************************Unterprogramme von main*****************************************
InOut:
    ldi temp, 0b11110011        ;Port D bis auf PD2 und PD3 ist 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, 0b00011111         ;Port C ist Ausgang
    out DDRC, temp
    ret

interrupts:

    ldi temp, 0b00001111 ; INT0 und INT1  auf steigende Flanke konfigurieren   
    out MCUCR, temp

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

stoppuhr:
                                    ; Vergleichswert 
        ldi     temp, high( 8000 - 1 )
        out     OCR1AH, temp
        ldi     temp, low( 8000 - 1 )
        out     OCR1AL, temp
                                    
                                    
        ldi     temp, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
        out     TCCR1B, temp
 
        ldi     temp, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
        out     TIMSK, temp
        ret



multiplextimer:     ;für die multiplexfrequenz

        ldi     temp, ( 0 << CS01 ) | ( 0 << CS00 ) | ( 0 << CS02 )
        out     TCCR0, temp
 
        ldi     temp, 1 << TOIE0
        out     TIMSK, temp
        ret
clear:

    clr     Hundertstel             ; Die Uhr auf 0 setzen
    clr     Sekunden
    clr     Minuten
    clr     SubCount
    clr     Flag                ; Flag löschen
    clr     null
    clr     ausschalten
    clr     zaehler
    clr     Zehner
    clr     schalter
    ret

;****************************EXTERNE INTERRUPTS********************************
;******************************************************************************
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
 
interrupt1:
         push temp             ; Das SREG in temp sichern. 
         in   temp, SREG       
 
         rcall stopp           ;Timer stoppen
         ldi flag,1            ;Berechnung einleiten

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


;********************************Unterprogramme von Interrupts******************************
start:

        ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
        out     TCCR1B, temp
        ret
stopp:

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

;*****************************Timer1 Überlauf/ STOPPUHR**************************************
timer1_compare:

        push    temp                ; temp 1 sichern
        in      temp,sreg           ; SREG sichern
 
        inc     SubCount            ; Wenn dies nicht der 10. Interrupt
        cpi     SubCount, 10        ; ist, dann passiert gar nichts
        brne    ende

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

                                    ; Überlauf von Hundertstel 
        clr     Hundertstel             ; Hundertstel wieder auf 0 und dafür
        inc     Sekunden            ; plus 1 Sekunde
        cpi     Sekunden, 60        ; sind 60 Minuten vergangen ?
        brne    ende                ; 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    ende                ; wieder zurücksetzen -> Minuten nur ein Segment, größte Zahl ist 9
                                   
        clr     Minuten             ; Minuten rücksetzen

        out     sreg,temp           ; sreg wieder herstellen
        pop     temp            ; Minuten rücksetzen
        reti

ende:
 
        out     sreg,temp          ; sreg wieder herstellen
        pop     temp
        reti                

Codes: ;Tabelle für die Codes                           
.db 0b11000000,0b11111001,0b10100100,0b10110000,0b10011001,0b10010010,0b10000010,0b11111000,0b10000000,0b10010000
                              

.DSEG
Seg_Code1:    .byte 1         ; Ausgabemuster für Segment 1
Seg_Code2:    .byte 1         ; Ausgabemuster für Segment 2
Seg_Code3:    .byte 1         ; Ausgabemuster für Segment 3
Seg_Code4:    .byte 1         ; Ausgabemuster für Segment 4
Seg_Code5:    .byte 1         ; Ausgabemuster für Segment 5


Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

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

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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.
Prg_Ctrl:  .Byte 1         ; Bit 0  Zeitzähler Freigabe starten
                           ; Bit 1  Zeitzähler läuft
                           ; Bit 2  Zeitzähler ist gestoppt
                           ; Bit 3  Freigabe Zeitzähler Reset
                           ; Bit 4  Reserve für Zwischenzeit...
                           ; Bit 5  Reserve
                           ; Bit 6  Reserve
                           ; Bit 7  Reserve
weitere Variablen.....
[/avrsam]

In der Timer ISR fragst du lediglich im Bereich mSek ^2 ab, ob Bit 1 gesetzt ist
[avrasm]
  Lds   Reg_a, Prg_Ctrl
  Andi  Reg_a, 0b00000010
  BREQ  Weiter_x
  RCALL Stopp_Uhr           ; Aufruf der Stoppuhr
weiter_x:        
  ....

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

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi
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
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....
;***************Unterprogramme von Interrupts**************************
start:

        ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
        out     TCCR1B, temp
        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

Autor: Vuvuzelatus (Gast)
Datum:

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

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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
  push temp             ; erst temp sichern.
  in   temp, SREG 
  push Temp             ; Jetzt SREG sichern
  .....
End_ISR_Prog:           ; mit Sprüngen auf diese Marke aussteigen
                         
  POP  Temp             ; das ist jetzt SREG
  Out  SREG, Temp       ; also SREG auf alten Stand
  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

Autor: Vuvuzelatus (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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 ...
>interrupt0:
         push temp             ; Das SREG in temp sichern.
         in   temp, SREG       
          
         ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )      ;Stoppuhr starten
         out     TCCR1B, temp

         out SREG, temp        ; Die Register SREG und temp wieder
         pop temp              ; herstellen
         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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

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

stoppuhr:
                                    ; Vergleichswert 
        ldi     temp, high( 8000 - 1 )
        out     OCR1AH, temp
        ldi     temp, low( 8000 - 1 )
        out     OCR1AL, temp
                                    
                                    
        ldi     temp, 0b00001000   ; CTC Modus einschalten /Timer noch nicht gestartet.
        out     TCCR1B, temp
 
        ldi     temp, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
        out     TIMSK, temp
        ret



multiplextimer:     ;für die multiplexfrequenz

        ldi     temp, ( 0 << CS01 ) | ( 0 << CS00 ) | ( 0 << CS02 )
        out     TCCR0, temp
 
        ldi     temp, 1 << TOIE0
        out     TIMSK, temp
        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)

Autor: Vuvuzelatus (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:

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

Autor: Vuvuzelatus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Und das entspricht deinem Programm.

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

Autor: spess53 (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:
Angehängte Dateien:

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

Autor: Christian (Gast)
Datum:
Angehängte Dateien:

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

Grüße
Jörn

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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:
Zeitzaehler:
   Push Reg_A             ; Aufruf aus ISR, daher Register sichern !
   LDS  Reg_A, Zaehler_L  ; niederwertigen Zähler laden
   INC  Reg_A
   STS  Zaehler_L         ; zurückschreiben
   BRNE End_Zaehler       ; Überlauf durch 255+1 ergibt 0
   LDS  Reg_A, Zaehler_H  ; dann das High-Byte incrementieren
   INC  Reg_A
   STS Zaehler_H, Reg_A   ; und zurückschreiben
End_Zaehler:              
   LDS  Reg_A, Counter_Ctrl
   ORI  Reg_A, 0b00000001 ; Flag für Anderung Zähler
   STS  Counter_Ctrl, Reg_A
   POP  Reg_A             ; und POP hinter die End-Marke !
RET
Den folgenden Programmblock rufst du aus deiner Programmschleife auf, 
wenn du erkennst, das das Bit in Counter_Ctrl gesetzt ist.
Berechne:                 
   LDS Reg_AH, Zaehler_H
   LDS Reg_AL, Zaehler_L
   CLR Reg_BH
   LDI Reg_BL,10
   RCALL Div_Word
   STS  Msek_Anz2, Reg_BL     ; für 1/100 Sekunden
   LDI Reg_BL,10
   RCALL Div_Word             ; in Reg_AH und Reg_AL ist ja das Ergebnis
   STS  Msek_Anz1, Reg_BL     ; für 1/10 Sekunden
   LDI Reg_BL,60
   RCALL Div_Word             ; Ergebnis Sekunden in Reg_AH und Reg_AL 
   STS  Reg_AH, Min_H         ; Minuten merken 
   STS  Reg_AL, Min_L         ; Minuten merken 
   Mov  Reg_AH, Reg_BH        ; Sekunden in Divident
   Mov  Reg_AL, Reg_BL
   CLR  Reg_BH
   LDI  Reg_BL, 10 
   RCALL Div_Word             ; Ergebnis in Reg_AL und Reg_BL (Sekunden)
   STS  Sek_Anz0, Reg_BL
   STS  Sek_Anz1,Reg_AL
   LDS  Reg_AH, Min_H
   LDS  Reg_AL, Min_L
   CLR  Reg_BH
   LDI  Reg_BL,10
   ...                        ; den Rest solltest du schon selbst finden
   RCALL Set_Code
   LDS  Reg_A, Counter_Ctrl
   ANDI Reg_A, 0b11111110     ; Flag zurücksetzen
   STS  Counter_Ctrl, Reg_A   
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

Autor: Vuvuzelatus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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):
; input : t (Byte im Bereich 0...99)
; output: r1, r0 (Einer in r0, Zehner in r1)

ByteBinToBCD:
    mov    r0, t
    clr    r1
    ldi    a, 10

ByteBinToBCDLoop:
    inc    r1
    sub    r0, a
    brsh   ByteBinToBCDLoop

    dec    r1
    add    r0, a

    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:
ByteBinToSevenSegPattern:
    mov    r0, t
    clr    r1
    ldi    a, 10

ByteBinToSevenSegPatternLoop:
    inc    r1
    sub    r0, a
    brsh   ByteBinToSevenSegPatternLoop

    dec    r1
    add    r0, a

    ldi    ZL, Low (SRAM_SEVENSEGMENTPATTERN)
    ldi    ZH, High(SRAM_SEVENSEGMENTPATTERN)
    add    ZL, r0
    ld     r0, Z    

    ldi    ZL, Low (SRAM_SEVENSEGMENTPATTERN)
    ldi    ZH, High(SRAM_SEVENSEGMENTPATTERN)
    add    ZL, r1
    ld     r1, Z    

    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:
  mov   t, Hundertstel
  rcall ByteBinToSevenSegPattern
  sts   SRAM_SEGCODE1, r0
  sts   SRAM_SEGCODE2, r1

  mov   t, Sekunde
  rcall ByteBinToSevenSegPattern
  sts   SRAM_SEGCODE3, r0
  sts   SRAM_SEGCODE4, r1

  mov   t, Minute
  rcall ByteBinToSevenSegPattern
  sts   SRAM_SEGCODE5, r0
  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:
    clr   ZL
    clr   ZH
    sbiw  ZL, 1
    brne  PC-1

braucht gut 262000 Taktzyklen.

Autor: Martin Vogel (oldmax)
Datum:

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

Autor: Christian (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:
Angehängte Dateien:

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

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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):
  lds r16,segcodex
  lsr r16
  lsr r16
  andi r16,0b00111100
  lds r17,segcodex
  andi r17,0b00000011
  or r16,r17
  in r17,PortD
  andi r17,0b11000000
  or r17,r16
  out PortD,r17

  lds r16,segcodex
  lsr r16
  andi r16,0b00000011
  in r17,PortB
  andi r17,0b11111100
  or r17,r16
  out PortB,r17

MfG Spess

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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
;********************************************
;* Variable Seg_Pos : Bit 0 Segment 10^0    *
;*                    Bit 1 Segment 10^1    *
;*                    ... usw ....          *
;*          Segment: Bit 0 = LED a          *
;*                   Bit 1 = LED b          *
;*                   .... usw ....          *
;********************************************
My_UnKnown_Sub:

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

Autor: Christian (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi Spess,

leider funktioniert es nicht.

Auf meine Anzeigeroutine angewendet sieht es folgendermaßen aus.
anzeige1:
  push temp             ; erst temp sichern.
  in   temp, SREG 
  push Temp             ;dann SREG
  cpi zaehler, 1
  brsh anzeige2
  out PORTC, ausschalten
;**********************************
  lds temp, Seg_Code1
  lsr temp
  lsr temp
  andi temp,0b00111100
  lds temp1,Seg_Code1
  andi temp1,0b00000011
  or temp,temp1
  in temp1,PortD
  andi temp1,0b11000000
  or temp1,temp
  out PortD,temp1

  lds temp,Seg_Code1
  lsr temp
  andi temp,0b00000011
  in temp1,PortB
  andi temp1,0b11111100
  or temp1,temp
  out PortB,temp1
;*********************************
  inc schalter
  out PORTC, schalter
  inc zaehler
  rjmp anzeige_ende
  [...]

anzeige_ende:
  pop  Temp             ; das ist jetzt SREG
  out  SREG, Temp       ; also SREG auf alten Stand
  pop  Temp             ; und jetzt Temp wieder herstellen
  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

Autor: spess53 (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

Okay. dann probiere mal das:
  lds r18,segcodex     ; Für jede Stelle
  rcall abcd
  out PortD,r16
  out PortB,r17


; Das ganze als Unterprogramm
abcd:                          
  mov r16,r18
  lsl r16
  lsl r16
  andi r16,0b00111100
  mov r17,r18
  andi r17,0b00000011
  or r16,r17
  in r17,PortD
  andi r17,0b11000000
  or r16,r17
  push r16

  mov r16,r18
  swap r16
  lsr r16
  lsr r16
  andi r16,0b00000011
  in r17,PortB
  andi r17,0b11111100
  or r17,r16             ;Wert für PortB
  pop r16                ;Wert füt PortD
  ret

MfG Spess

Autor: Martin Vogel (oldmax)
Datum:

Bewertung
0 lesenswert
nicht 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.
;***************************************************
; Selektieren und ansteuern von 7 Segmentanzeigen  *
;* Seg_Pos   Bit 0 -6 Selektion Segment            *
;* Matrix    Bit 0-7 LED a-f                       *
;* Port      PortD  Bit 0 = a                      *
;*                  Bit 1 = b                      *
;*                  Bit 4 = c                      *
;*                  Bit 5 = d                      *
;*                  Bit 6 = e                      *
;*                  Bit 7 = f                      *
;*           PortB  Bit 0 = g                      *
;***************************************************
Set_SegAnz:
   Push Reg_A
   Push Reg_B
   LDS Reg_A, Matrix    ; Holen der Zeichenmatrix
   POP Reg_A            ; Zwischenspeichern, wird nochmal gebraucht
   ANDI Reg_A, 0b01000000 ; Bit LED g ausmaskieren (0x000000)
   SWAP Reg_A             ; Bit g ist jetzt auf pos. Bit 2 (00000x00)
   ROR  Reg_A             ; Position Bit 1  (000000x0)
   ROR  Reg_A             ; Position Bit 0  (0000000x)
   ANDI Reg_A, 0b00000001 ; nur die 1 auf bit 0 stehen lassen
   In   Reg_B, PInB       ; Port lesen, da nur Bit 0 verändert werden soll
   ANDI Reg_B, 0b11111110 ; Bit 0 löschen, da x auch eine 0 ergeben kann
   Or   Reg_B, Reg_A      ; Reg_Bmit Reg_A verodern (xxxxxxx0 mit 0000000x)
   Out  PortB, Reg_B
   POP Reg_A
   ANDI Reg_A, 0b00111111 ; Jetzt die LED a-f behandeln
......

   POP Reg_B
   POP Reg_A
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

Autor: Vuvuzelatus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
  ; t enthält das Siebensegmentmuster
  bst t, 0
  bld pod, PD0
  bst t, 1
  bld pod, PD1
  bst t, 2
  bld pod, PD4
  bst t, 3
  bld pod, PD5
  bst t, 4
  bld pod, PD6
  bst t, 5
  bld pod, PD7
  bst t, 6
  bld pob, PB0
  bst t, 7
  bld pob, PB1

  out PORTB, pob
  out PORTC, poc
  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.

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

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

Soll doch heutzutage geil sein.

MfG Spess

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.
  .....
  lds r18,segcodex     ; Aufruf
  rcall abcd           ; für jede Stelle
  out PortD,r16
  out PortB,r17
  ....

abcd:
  mov r16,r18          ; r16 = dp,gfedcba
  andi r16,0b00000011  ; r16 = 000000ba

  clr r17
  lsl r18              ;    r17          r18
  rol r17              ; 0000000,dp    gfedcba0
  lsl r18
  rol r17              ; 000000,dp,g   fedcba00
  andi r18,0b11110000  ; r18 = fedc0000
  or r16,r18           ; r16 = fedc00ba

; Mit den Ports verknüpfen
  in r18,PortD
  andi r18,0b00001100
  or r16,r18           ; Wert für PortD

  in r18,PortB
  andi r18,0b11111100
  or r17,r18           ; Wert für PortB

  ret

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

MfG Spess

Autor: Christian (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:

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

Autor: Christian (Gast)
Datum:
Angehängte Dateien:

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

Autor: spess53 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hi

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

MfG Spess

Autor: Martin Vogel (oldmax)
Datum:

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

Autor: Jörn Christian (Gast)
Datum:
Angehängte Dateien:

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

Autor: spess53 (Gast)
Datum:

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

Autor: Jörn Christian (Gast)
Datum:

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

Autor: spess53 (Gast)
Datum:
Angehängte Dateien:

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

Autor: oldmax (Gast)
Datum:

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

Autor: Jörn Christian (Gast)
Datum:
Angehängte Dateien:

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

Antwort schreiben

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

Wichtige Regeln - erst lesen, dann posten!

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

Formatierung (mehr Informationen...)

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




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

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