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
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?
Hi
Auch die bessere Version funktioniert sicherlich auch nicht....
Also deine Programmschleife solltest du dir mal genauer ansehen. da ist
es möglich, das du direkt in die ISR rauscht, und das ohne irgend eine
RCALL. Irgendwann kommt dann ein netter RETI und schwups, ist dein Stack
im Nirwana...
Also, bei einem Assemblerprogramm solltest du folgendes beherzigen:
Viele kleine Routinen und auch das EVA-Prinzip ! (Eingabe, Verarbeitung,
Ausgabe)
Die Initialisierung der einzelnen Module ( IO, Timer, UART, Defaults
etc.) in einzelne kleine Routinen gepackt und mit RCALL aufgerufen macht
dein Programm übersichtlich.
In der Programmschleife ist die letzte Anweisung ein "RJmp MainLoop" !
Danach kannst du die Routinen für die einzelnen Aufgaben in
Unterprogramme fassen.
Wenn du mit Assembler arbeitest und eine serielle Schnittstelle hast,
kannst du mit OpenEye dir zur Laufzeit die Variablen ansehen. OpenEye
ist ein kleines PC Programm von mir. Hab ich hier irgendwo mal
veröffentlicht. Mir hilft es immer wieder, wenn ich nicht verstehe,
warum der µC anderer Meinung ist wie ich.
Zu deinem Programm vielleicht noch ein Tip:
Einlesen der Eingänge und ablegen dr IO-Bits in einer Variablen. Danach
eine Flanke bilden, welche Eingänge sich geändert haben. Den Zeitwert
packst du in eine weitere Variable, die du im Msek.Takt hochzählst.
Keine Angst, mit 8 MHz ist dein µC locker in der Lage...
Mit einer 32-Bit Division (findest du auch hier...) setzt du dann die
Zahlenwerte
Zum Verständnis:
Zähler Integerwert: 34229 ( reichen 64 Sek. dann kannst du so hoch
auflösen)
Ziffern bilden
Wert /10 ergibt 3422 Rest 9- Stelle 1
Wert /10 ergibt 342 Rest 2- Stelle 2
Wert /10 ergibt 34 Rest 2- Stelle 3
Wert /10 ergibt 3 Rest 4- Stelle 4
Wert 3 = stelle 5
Ein Variablenblock Matrix_0 bis Matrix_9 setzt du mit den Defautwerten
für die 7 Segmentanzeige. Bitmuster 0-7 = Segment a-g
Über die berechnete Zahl und der Adressierung über ein Doppelregister
mit dem Offset der Zahl erhälst du den 7 Segmentcode.
etwa so:
1
LDS Reg_A, Wert_1
2
LDI Reg_B, 0
3
LDI XL,LOW(Matrix_0) ; x-Pointer 1.Matrixwert
4
LDI XH,HIGH(Matrix_0)
5
ADD XL, Reg_A
6
ADC XH, Reg_B ; zur Matrixadresse addieren
7
LD Reg_A, x
8
STS Seg_Code_1,Reg_A
Ist nur ein kleiner Auszug, aber mit ein wenig Überlegung wirst du
erkennen, das es ganz leicht ist, damit eine Multiplex-Routine für die
Ausgabe zu füttern. Die Ausgabe der Variablen Seg_Cod 1 - Seg_Code5 habe
ich dann in den Timer gepackt und rufe im mSek. Takt diese Routine zur
Ausgabe auf. Dadurch ist die Anzeige unabhängig von Programmlaufzeiten
und "flimmert" nicht. Wohlgemerkt, die Aufbereitung der 7 Segment-Codes
erfolgt im normalen Programm.
Gruß oldmax
philipp schrieb:> schau mal bei den Lcd-routines, ziemlich weit unten ist sowas, musst du> halt umbasteln für 7 segment. das dröselt die zahl in 2 bzw 3 stellen> auf. kriegst du die 7 segment schon angesteuert?
Vielen Dank, das hatte ich übersehen. Ich glaube ich hab es jetzt
geschafft die Zahlen in 2 Stellen aufzuspalten.
Mit der Ansteuerung der Segmente hapert es noch an der Codetabelle, aber
dazu später mehr.
Erstmal vielen Dank oldmax das du dir die Mühe gemacht hast eine so
ausführliche Antwort zu schreiben.
oldmax schrieb:> Hi> Auch die bessere Version funktioniert sicherlich auch nicht....
Hab das besser auf die Lesbarkeit bezogen, nicht auf den Inhalt :)
oldmax schrieb:> Also, bei einem Assemblerprogramm solltest du folgendes beherzigen:> Viele kleine Routinen und auch das EVA-Prinzip ! (Eingabe, Verarbeitung,> Ausgabe)> Die Initialisierung der einzelnen Module ( IO, Timer, UART, Defaults> etc.) in einzelne kleine Routinen gepackt und mit RCALL aufgerufen macht> dein Programm übersichtlich.
Ich hab das Programm jetzt mal ein bisschen aufgeräumt und alles schön
wie im Informatikunterricht gelernt in Unterprogramme aufgeteilt.
Wenn ich mir das so betrachte sah das erste Programm ja unter aller
Kanone aus.
oldmax schrieb:> Wenn du mit Assembler arbeitest und eine serielle Schnittstelle hast,> kannst du mit OpenEye dir zur Laufzeit die Variablen ansehen. OpenEye> ist ein kleines PC Programm von mir. Hab ich hier irgendwo mal> veröffentlicht. Mir hilft es immer wieder, wenn ich nicht verstehe,> warum der µC anderer Meinung ist wie ich.
Serielle Schnittstelle habe ich, nur leider kein Kabel. Mit dem
AVRStudio kann ich mir im Debuggmodus doch auch die Register und die
darin enthaltenen Variablen ansehen oder meinst du etwas anderes?
oldmax schrieb:> Mit einer 32-Bit Division (findest du auch hier...) setzt du dann die> Zahlenwerte> Zum Verständnis:> Zähler Integerwert: 34229 ( reichen 64 Sek. dann kannst du so hoch> auflösen)> Ziffern bilden> Wert /10 ergibt 3422 Rest 9- Stelle 1> Wert /10 ergibt 342 Rest 2- Stelle 2> Wert /10 ergibt 34 Rest 2- Stelle 3> Wert /10 ergibt 3 Rest 4- Stelle 4> Wert 3 = stelle 5
Theoretisch würden 64 Sekunden reichen, ich würde jedoch gerne die Zeit
nach folgendem Schema anzeigen:
[Minuten]---[Sekunden][Sekunden]---[Hundertstel][Hundertstel]
Die Klammern sollen jeweils einen 7Segment-Baustein darstellen.
oldmax schrieb:> Ein Variablenblock Matrix_0 bis Matrix_9 setzt du mit den Defautwerten> für die 7 Segmentanzeige. Bitmuster 0-7 = Segment a-g> Über die berechnete Zahl und der Adressierung über ein Doppelregister> mit dem Offset der Zahl erhälst du den 7 Segmentcode.> etwa so:>
1
> LDS Reg_A, Wert_1
2
> LDI Reg_B, 0
3
> LDI XL,LOW(Matrix_0) ; x-Pointer 1.Matrixwert
4
> LDI XH,HIGH(Matrix_0)
5
> ADD XL, Reg_A
6
> ADC XH, Reg_B ; zur Matrixadresse addieren
7
> LD Reg_A, x
8
> STS Seg_Code_1,Reg_A
9
>
Ich hab das mal versucht in den Code einzubauen, jedoch müsste ich dann
wegen der Padding Bytes das Doppelregister mit dem zweifachen Wert laden
oder?
oldmax schrieb:> Ist nur ein kleiner Auszug, aber mit ein wenig Überlegung wirst du> erkennen, das es ganz leicht ist, damit eine Multiplex-Routine für die> Ausgabe zu füttern. Die Ausgabe der Variablen Seg_Cod 1 - Seg_Code5 habe> ich dann in den Timer gepackt und rufe im mSek. Takt diese Routine zur> Ausgabe auf. Dadurch ist die Anzeige unabhängig von Programmlaufzeiten> und "flimmert" nicht. Wohlgemerkt, die Aufbereitung der 7 Segment-Codes> erfolgt im normalen Programm.
Hier komme ich einfach nicht weiter.
Ich verstehe nicht wie ich es anstellen soll das der korrekte
LED-Baustein angeschaltet und gleichzeigt das richtige Muster ausgegeben
wird. Das muss auch noch an allen 5 Bausteinen nacheinander und richtig
passieren.
Es wäre fantastisch wenn du mir vielleicht nochmal ein kleines
Codeschnipsel liefern könntest und ich mir dann überlegen kann wie ich
es lösen könnte.
Bei einer Facharbeit soll man ja schließlich etwas lernen.
Ein weiteres Problem das sich auftut ist die Codetabelle mit den
Bitmustern für die Anzeige von Zahlen.
So wie ich die Tabelle jetzt aufgebaut habe stimmen die Muster nur wenn
alles an PortD ausgegeben wird. Bei mir sind aber PD2 und PD3 durch die
Interrupteingänge belegt und ich muss die letzten beiden Anschlüsse auf
PortB legen.
a = PD0
b = PD1
c = PD4
d = PD5
e = PD6
f = PD7
g = PB0
h = PB1 (Dezimalpunkt; wobei ich den Anschluss eigentlich nicht
bräuchte)
Wie berücksichtige ich diese Änderung in der Codetabelle.
Ich füge nochmal meinen etwas überarbeiteten Code hinzu, wobei dies kein
fertiger Code ist, sondern nur meine Ideen und meinen jetztigen Stand
darstellt.
Tut mir Leid das ich euch so mit Fragen bombardiere, aber ich komme an
diesem Punkt einfach nicht mehr weiter.
Beste Grüße
Christian
Hi
>Ich hab das mal versucht in den Code einzubauen, jedoch müsste ich dann>wegen der Padding Bytes das Doppelregister mit dem zweifachen Wert laden>oder?
Das trifft nur auf das Codesegment zu, im SRam (DSeg) und auch im
EERprom adressierst du jede Speicherzelle. Daher liegen die Variablen
für den Segmentcode, ich nenne sie mal Matrix_0 bis Matrix_9, auch im
Bereich X+n (n =0 bis 9)
Daher kannst du, wenn du eine einstellige Zahl hast, diese über die
Matrix zum 7-Segment-Code umsetzen.
Was in Assembler sicherlich nicht einfach ist, ist Mathematik
anzuwenden. Auch ich tu mich da ein wenig schwer und hab deswegen auch
ein bisschen von den richtigen Fachleuten abgeschaut... KHB hat irgendwo
hier eine 32 Bit Division veröffentlicht, die ich mir auf eine 16 Bit
Division umgekupfert habe.
1
;zuerst mal Register definieren
2
.Def mSek_0 = r1 ; Zählregister 1/1000 in ISR
3
.Def mSek_1 = r2 ; Zählregister für 1/100 Sekunden in ISR
4
.Def msel_2 = r3 ; Zählregister für 1/10 Sekunden
5
.Def Dekade = r4 ; Register für Vergleich auf 10 in ISR
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
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
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 :)
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.
Hi
Wenn es nicht grad "Deine" Facharbeit wäre.....
Also, so ganz hast du mich noch nicht verstanden. Alle Initialisierungen
werden nur EINMAL gemacht. Danach sollte dein Timer, wie du es auch
schreibst, in mSek.Takt einen Interrupt auslösen. Nun zählst du mSek ^0,
mSek ^1, msek ^2, Sekunden, Minuten und wenns interessiert bis hin zu
Jahren.
Da im Bereich der mSek. möglichst schneller Code sein sollte, verwende
ich da Register, die ausschließlich mSek. zählen. Danach nehme ich
Variablen. Ob nun 1 mal in der Sekunde eine Variable zusätzlich geladen
und wieder zurückgespeichert wird, ist so ziemlich egal. Die
Programmzeit erhöht sich da lediglich um ca. 4 Taktzyklen. ( bei 8 MHz
0,5 µSek )
Dann mußt du bei Push und POP auch konsequent sein und verwendete
Register sichern. Klar, das Register Temp nutzt du nicht ein zweitesmal
in der ISR, so das dein SREG vermutlich auch wieder mit OUT
zurückgeschrieben wird. Aber, rufst du in der ISR Unterprogramme auf, so
sind diese darin verwendeten Register ebenfalls zu sichern. Du
erweiterst ja nur die ISR.
@KHB
Der Tipp mit den kleinen Unterprogrammen ist schon ernst gemeint.
Wenn man eine Funktion zusammenfassen kann, sollte man es auch tun.
Dadurch begrenzt sich auch die Fehlersuche. Und wenn man es richtig
macht, behält man auch den Überblick.
Solche kleinen Routinen drucke ich mir bspw. aus. Normalerweise paßt
eine Routine auf ein DinA4 Blatt. Diese kann man dan abheften und
gezielt nachschlagenn nebeneinanderlegen oder was auch immer. Klar, wenn
man nur im Monitor hin und herscrollt, da gibt's dann schon mal ein
Problem mit dem Durchblick.
Auch hilft ein Programmablaufplan. Da sieht man dann auch, welche
Subroutinen aus einer ISR heraus aufgerufen werden und ob deshalb die
Register gesichert werden müssen.
Ich hab auch nicht verstanden, warum beim stoppen der Zeit die Anzeige
nicht funktionieren soll. Den Hinweis, den Aufruf dafür in die ISR
einzubinden und im msek. Takt den Multiplexer aufzurufen hatte seinen
Grund. Ich hab mit 8 MHz eine saubere Anzeige erzielt. Warum sollte dies
bei dir anders sein. Das Umsetzen der Anzeigencodes machst du im
Programm. Und benenne deine Routinen ruhig mit Init_IO. So hast du eine
klare Aussage.
Du hast ja schon einen Ansatz mit einer Variablen Flag gemacht. Geh doch
einen Schrit weiter und nehme die einzelnen Bits. Ich nenne solche
programmsteuernden Variablen Prg_Ctrl.
1
Prg_Ctrl: .Byte 1 ; Bit 0 Zeitzähler Freigabe starten
2
; Bit 1 Zeitzähler läuft
3
; Bit 2 Zeitzähler ist gestoppt
4
; Bit 3 Freigabe Zeitzähler Reset
5
; Bit 4 Reserve für Zwischenzeit...
6
; Bit 5 Reserve
7
; Bit 6 Reserve
8
; Bit 7 Reserve
9
weitere Variablen.....
10
[/avrsam]
11
12
In der Timer ISR fragst du lediglich im Bereich mSek ^2 ab, ob Bit 1 gesetzt ist
13
[avrasm]
14
Lds Reg_a, Prg_Ctrl
15
Andi Reg_a, 0b00000010
16
BREQ Weiter_x
17
RCALL Stopp_Uhr ; Aufruf der Stoppuhr
18
weiter_x:
19
....
Das Bit Freigabe starten brauchst du bspw. um die 1. Lichtschranke
freizugeben, damit nicht ein zufällig durchgewehtes Blatt dem Zähler die
Freigabe gibt. Also eine Eingabe für Startfreigabe.
Um deine Anzeige wieder zurückzusetzen, brauchst du auch das Signal
"Zähler gestoppt". Es gibt andere Bedingungen, wie z.B. die
Tasteneingabe Startfreigabe, die nur einmal behandelt werden darf, also,
wo du die Flanke brauchst, um dieses dann wieder nach Bearbeitung durch
Löschen des Flankenbits zu quittieren. In diesem Fall setzt du das Bit 0
in Prg_Ctrl und löscht das Flankenbit der Taste.
(Zu Flankenerkennung ist hier auch schon eine Menge geschrieben worden.
Sollte dir das nicht klar sein, frag halt nochmal. )
Daher wirst du immer wieder über eine Und-Verknüpfung ein einzelnes Bit
aus einem Byte herausmaskieren müssen, um den Status abzufragen. Schlagt
mich nicht, es gibt glaube ich auch eine TST - Anweisung. Aber alle
Assemblerbefehle hab ich nicht im Kopf., dafür ist aber die Hilfe in AVR
Studio da ganz gut bestückt.
Gruß oldmax
out SREG, temp ; Die Register SREG und temp wieder
8
pop temp ; herstellen
9
reti
Das ist deine ISR. Du sicherst Temp und das SREG in Temp. Temp hat hier
bis ende der ISR scheinbar keine Änderung und so speichert du das SREG
direkt zurück. Wär kein Problem, wenn da nicht dieser RCALL wäre....
1
;***************Unterprogramme von Interrupts**************************
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
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.
Hi
Deinen Worten stimme ich nicht so blauäugig zu. Wenn du in einer ISR
eine Anzeige multiplext, so wie ich es beschrieben habe und Eingänge per
Interrupt abfragst bzw. bearbeitest, mußt du schon damit rechnen, das
ein sehr kurzes Signal in der Timer-ISR unterschlagen wird, wenn du
diese Interrupts nicht zuläßt. Dann brauchst du auch keine
Signalerfassung mit Interrupt bearbeiten, wenn du dieses einfach
ignorierst. Wieviel mehr Takte benötigt es denn, ein gerettetes SREG
auch über das Register auf den Stack zu schreiben... auch nur 4
Taktzyklen, 2 für Push und 2 für POP. Bei 0,5 µSek ist das grad mal
1/2000 der ISR- Aufrufzeit von 1 mSek.
Also es spricht nichts gegen
1
push temp ; erst temp sichern.
2
in temp, SREG
3
push Temp ; Jetzt SREG sichern
4
.....
5
End_ISR_Prog: ; mit Sprüngen auf diese Marke aussteigen
6
7
POP Temp ; das ist jetzt SREG
8
Out SREG, Temp ; also SREG auf alten Stand
9
POP Temp ; und jetzt Temp wieder herstellen
Auch ist es sinnvoll, immer nur einen Ausstieg aus einer Routine zu
haben. Muß man mal ein weiteres Register innerhalb einer ISR verwenden,
braucht es nur an einer Stelle zurückgepopt werden. Unterprogramme mit
mehr als einem RET oder RETI sollte man sich gar nicht erst angewöhnen.
Auch wenn es bei den Profis wohl "normal" ist.
Gruß oldmax
>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.
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
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 ...
out SREG, temp ; Die Register SREG und temp wieder
9
pop temp ; herstellen
10
reti
... dann hättest du gesehen, dass temp (und damit die Sicherung des
SREG) innerhalb der ISR zerstört wird. Man kann alles übertreiben. Auch
die Aufteilung in Unterprogramme und hin und her springen.
Martin Vogel schrieb:> @KHB> Der Tipp mit den kleinen Unterprogrammen ist schon ernst gemeint.>> Wenn man eine Funktion zusammenfassen kann, sollte man es auch tun.> Dadurch begrenzt sich auch die Fehlersuche. Und wenn man es richtig> macht, behält man auch den Überblick.
Du sagst es, WENN man es richtig macht.
So
1
stoppuhr:
2
; Vergleichswert
3
ldi temp, high( 8000 - 1 )
4
out OCR1AH, temp
5
ldi temp, low( 8000 - 1 )
6
out OCR1AL, temp
7
8
9
ldi temp, 0b00001000 ; CTC Modus einschalten /Timer noch nicht gestartet.
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)
>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.
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
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
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
Hi
So langsam nähern wir uns ja einem laufenden Programm. Also, schneller
Code heißt nix anderes, das man möglichst wenig Befehle setzt. Wenn ich
eine Variable nehme, muß ich diese erst in ein Register laden und dann
wieder zurückschreiben. Das sind 4 Taktzyklen mehr. Im Msek. Bereich
möchte man aber sicherstellen, das die gesamte Programmbearbeitung
innerhalb der ISR weit unter dieser mSek. bleibt, weil sonst das
Hauptprogramm, welches durch eine ISR ja ständig unterbrochen werden
kan, überhaupt nicht mehr zum Zuge kommt. Daher bin ich in den
"schellen" Teilen einer ISR schon mal geizig, was Befehle betrifft und
opfer da ein Register. Der Atmega hat ja auch genug.
Was deine Berechnung betrifft, warum nimmst du nicht den Teiler ? Er ist
wesentlich effizienter, als deine Subtraktion.
Wenn du es nicht verstanden hast, versuch ich es nochnal zu erklären.
Dein Denken basiert mathematisch im 10er Bereich. Du kennst Ziffern von
0-9. Nun schreib mal eine Division zweier Zahlen auf, so wie du es
gelernt hast. Die Einzelergebnisse können ebenfals den Wert von 0 bis 9
annehmen.
Nun dek mal binär. Da hast du nur Ziffern von 0 und 1. Also kann ein
Schrittergebnis auch nur 0 oder 1 sein. Auf dieser Basis arbeitet die
Ganzzahl Division von KHB. Ich hab diese nur auf 16 Bit reduziert, weil
es für meinen Bereich völlig ausreicht und dadurch natürlich auch
schneller ist. Das liegt daran, das dieses Programm eine eigene Schleife
hat und solange er in der Berechnung hängt, natürlich nichts im
Hauptprogramm macht.
Du hast deine ISR aufgebaut. Du weißt, das du 1/10 Sek. messen willst,
also in jeder 1/10 Sekunde erhöhst du deinen Zähler. Das geschieht in
der ISR, klar. Nun setzt du einfach ein Bit, was deinem Hauptprogramm
anzeigt, mein Zähler hat sich geändert.
Im Hauptprogramm fragst du dieses Bit ab und sollte es gesetzt sein,
springst du in eine Routine "Berechne".
Hier berechnest du die Zahlen und setzt die Codes, die vom Multiplexer
für die Anzeige gebraucht werden. Erinner dich, der Multiplexer wird im
mSek.-Takt aus der ISR aufgerufen.
Also, was muß in der Routine "Berechne" durchgeführt werden ? Klar,
deine 2Byte-Integer -Zahl muß erst mal durch 10 geteilt werden. Das
ganzzahlige Ergebnis bekommst du zurück und den Rest. Dwer ist natürlich
nur im Bereich von 0-9, also im Low-Byte der Rückgabewerte. Die Register
habe ich ja schon entsprechend benannt:
Reg_AL für Low-Byte und Reg_AH für High-Byte.
Also liefert Reg_AH und Reg_AL die 16 Bitzahl, die duch die 16-Bitzahl
in Reg_BH und Reg_BL geteilt wird. Das Ergebnis steht in Reg_AH und
Reg_AL sowie der Rest in Reg_BL. Um nun in "Berechne" den aktuellen
Zählerstand in eine Zahl für die anzeige zu wandeln, ist es ganz
einfach. Zuerst die Zehntel-Sekunden
Also Zähler, / 10. Ergebnis sind Sekunden und Rest sind 1/10 Sekunden.
Ergebnis nun durch 60 teilen. Ergebnis Minuten, Rest Sekunden. Da die
Sekunden noch zweistellig sind, die Minuten merken und die Sekunden
durch 10 teilen. Ergebnis dund Rest entsprechen den zwei
Sekundenziffern.
Gleiches noch mit Minuten und fertig.
Hier mal ein wenig Code, damit du die Angst vor solchen Programmen
verlierst:
1
Zeitzaehler:
2
Push Reg_A ; Aufruf aus ISR, daher Register sichern !
LDS Reg_A, Zaehler_H ; dann das High-Byte incrementieren
8
INC Reg_A
9
STS Zaehler_H, Reg_A ; und zurückschreiben
10
End_Zaehler:
11
LDS Reg_A, Counter_Ctrl
12
ORI Reg_A, 0b00000001 ; Flag für Anderung Zähler
13
STS Counter_Ctrl, Reg_A
14
POP Reg_A ; und POP hinter die End-Marke !
15
RET
Den folgenden Programmblock rufst du aus deiner Programmschleife auf,
wenn du erkennst, das das Bit in Counter_Ctrl gesetzt ist.
1
Berechne:
2
LDS Reg_AH, Zaehler_H
3
LDS Reg_AL, Zaehler_L
4
CLR Reg_BH
5
LDI Reg_BL,10
6
RCALL Div_Word
7
STS Msek_Anz2, Reg_BL ; für 1/100 Sekunden
8
LDI Reg_BL,10
9
RCALL Div_Word ; in Reg_AH und Reg_AL ist ja das Ergebnis
10
STS Msek_Anz1, Reg_BL ; für 1/10 Sekunden
11
LDI Reg_BL,60
12
RCALL Div_Word ; Ergebnis Sekunden in Reg_AH und Reg_AL
13
STS Reg_AH, Min_H ; Minuten merken
14
STS Reg_AL, Min_L ; Minuten merken
15
Mov Reg_AH, Reg_BH ; Sekunden in Divident
16
Mov Reg_AL, Reg_BL
17
CLR Reg_BH
18
LDI Reg_BL, 10
19
RCALL Div_Word ; Ergebnis in Reg_AL und Reg_BL (Sekunden)
20
STS Sek_Anz0, Reg_BL
21
STS Sek_Anz1,Reg_AL
22
LDS Reg_AH, Min_H
23
LDS Reg_AL, Min_L
24
CLR Reg_BH
25
LDI Reg_BL,10
26
... ; den Rest solltest du schon selbst finden
27
RCALL Set_Code
28
LDS Reg_A, Counter_Ctrl
29
ANDI Reg_A, 0b11111110 ; Flag zurücksetzen
30
STS Counter_Ctrl, Reg_A
31
RET
Diese so ermittelten Zahlen in den Variablen setzt du jetzt in den Code
für die Anzeigen um. Das dürfte nun aber wirklich kaum probleme
bereiten.
Wichtig ist, das du verstehst, welche Routinen mit der ISR verheiratet
werden müssen und welche von der Programmschleife bearbeitet werden.
Gruß oldmax
>Klar, deine 2Byte-Integer -Zahl muß erst mal durch 10 geteilt werden.
Ich frag mich echt, warum Du Christian unbedingt diese
16-Bit-Ganzzahldivision andienen willst. Er hat doch gar kein Problem,
zu dessen Lösung sie nötig wäre. Er muss nur für die drei Variablen
'Hundertstel', 'Sekunde' und 'Minute' eine Binär-zu-BCD-Umwandlung
durchführen. Alle drei Variablen sind ein Byte groß und liegen
garantiert im Bereich 0...99. Unter dieser Voraussetzung lässt sich die
Umwandlung mit acht Instruktionen erledigen ('ret' nicht mitgezählt):
1
; input : t (Byte im Bereich 0...99)
2
; output: r1, r0 (Einer in r0, Zehner in r1)
3
4
ByteBinToBCD:
5
mov r0, t
6
clr r1
7
ldi a, 10
8
9
ByteBinToBCDLoop:
10
inc r1
11
sub r0, a
12
brsh ByteBinToBCDLoop
13
14
dec r1
15
add r0, a
16
17
ret
Maximale Laufzeit = 48 Taktzyklen. Dein 'Div_Word' braucht bestenfalls
etwa 170 Takte. Versteh mich nicht falsch: Ich hab nix gegen diese
Routine, aber ich sehe auch keinen Sinn darin, eine 16-Bit-Operation
durchzuführen, wenn man nur 8-Bit-Variablen umwandeln will.
Da man nun die einzelnen Dezimalstellen in diesem Programm als solche
nirgendwo braucht, sondern nur ihre Siebensegment-Muster, würde ich die
Routine so erweitern, dass die Inhalte der Ergebnisregister r0 und r1
gleich an Ort und Stelle durch die Siebensegment-Muster ersetzt werden:
1
ByteBinToSevenSegPattern:
2
mov r0, t
3
clr r1
4
ldi a, 10
5
6
ByteBinToSevenSegPatternLoop:
7
inc r1
8
sub r0, a
9
brsh ByteBinToSevenSegPatternLoop
10
11
dec r1
12
add r0, a
13
14
ldi ZL, Low (SRAM_SEVENSEGMENTPATTERN)
15
ldi ZH, High(SRAM_SEVENSEGMENTPATTERN)
16
add ZL, r0
17
ld r0, Z
18
19
ldi ZL, Low (SRAM_SEVENSEGMENTPATTERN)
20
ldi ZH, High(SRAM_SEVENSEGMENTPATTERN)
21
add ZL, r1
22
ld r1, Z
23
24
ret
Die zehn Siebensegment-Muster müssen bei dieser Lösung im SRAM stehen.
Von da sind sie etwas bequemer zu laden als aus dem Programmspeicher
(Flash) mit der lpm-Instruktion. Ins SRAM bekommt man sie, indem man sie
im Zuge der Programminitialisierung einmal dorthin schreibt.
Befreit von jeder Redundanz wird 'rechnung' dann schön kompakt:
1
mov t, Hundertstel
2
rcall ByteBinToSevenSegPattern
3
sts SRAM_SEGCODE1, r0
4
sts SRAM_SEGCODE2, r1
5
6
mov t, Sekunde
7
rcall ByteBinToSevenSegPattern
8
sts SRAM_SEGCODE3, r0
9
sts SRAM_SEGCODE4, r1
10
11
mov t, Minute
12
rcall ByteBinToSevenSegPattern
13
sts SRAM_SEGCODE5, r0
14
sts SRAM_SEGCODE6, r1
@Christian: Wie Du siehst, kann man über das Z-Register indiziert auf
das SRAM zugreifen (mit X und Y gehts ebenso). Versuch mal, Dir das auch
in der ISR zunutze zu machen. Da steht nämlich auch noch viel mehr Code
drin als nötig.
>Also, schneller Code heißt nix anderes, das man möglichst wenig Befehle setzt.
Ich weiß, was Du meinst, aber trotzdem ist hier Vorsicht angebracht.
Manchmal ist nämlich auch gerade ein "großer" Code schneller als ein
kleiner, z. B. beim Entrollen von Schleifen. Christians Anzeige-ISR
ist zwar lang, aber trotzdem nicht übermäßig langsam, weil immer nur ein
relativ kleiner Teil (1/5) davon durchlaufen wird.
Auch kleine Codes können viel Zeit verschlingen, wenn mans drauf anlegt:
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
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
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
Hi
Also, ich würde in meinem Programm grundsätzlich erst mal die Bits in
einer Variablen haten. Was spricht dagegen. Mußt halt nur Laden und
wieder ablegen, wie gesagt, ein paar Taktzyklen mehr, aber von den
zeiten hab ich ja schon was geschrieben. Zur Ausgabe auf den Port rufst
du ein eigenes Unterprogramm auf. Klar, hier zwar indirekt aus der ISR
heraus, aber das hatten wir ja schon. Nun holst du die auszugebenden
Bytes und verteilst diese auf die Ausgänge. Hilfreich ist folgende
Vorgehensweise:
Bitmuster zurechtschieben
Port lesen
gesetzte Bits zuerst löschen ( Und Maskierung)
anschließend mit positionierten Bits verodern
und Port wieder beschreiben.
Nächsten Port....
Da du dies in einer eigenen Routine machst, kannst du dich hier völlig
auslassen und deine Bits verteilen wie's beliebt. Änderungen werden
ausschließlich hier bearbeitet. Ehrlich gesagt, für diese Hilfestellung
brauche ich hier nicht die Zuordnung der Portbits, es sei denn, ich
würde dir noch mehr Code zukommen lassen.
denk immer daran, wenn du etwas "zurechtbasteln" mußt und noch keinen
Durchblick hast, nimm Variablen und stütz dein Programm darauf ab. Die
Subroutine kannst du immer schreiben
1
;********************************************
2
;* Variable Seg_Pos : Bit 0 Segment 10^0 *
3
;* Bit 1 Segment 10^1 *
4
;* ... usw .... *
5
;* Segment: Bit 0 = LED a *
6
;* Bit 1 = LED b *
7
;* .... usw .... *
8
;********************************************
9
My_UnKnown_Sub:
10
11
RET
Wenn du dann dein Programm ausfüllen willst, brauchst du nur diese
kleine Routine bearbeiten. Kommentarzeilen helfen in diesem Fall, das du
vor Augen hast, was reinkommt. Es fehlt noch die Kommentierung, welche
Bits in welchen Port zugewiesen werden. Das macht dein Programm auch
noch nach 10 Jahren lesbar.... und diese Kommentarzeilen kosten im µC
keinen Speicher !
Gruß oldmax
Hi Spess,
leider funktioniert es nicht.
Auf meine Anzeigeroutine angewendet sieht es folgendermaßen aus.
1
anzeige1:
2
push temp ; erst temp sichern.
3
in temp, SREG
4
push Temp ;dann SREG
5
cpi zaehler, 1
6
brsh anzeige2
7
out PORTC, ausschalten
8
;**********************************
9
lds temp, Seg_Code1
10
lsr temp
11
lsr temp
12
andi temp,0b00111100
13
lds temp1,Seg_Code1
14
andi temp1,0b00000011
15
or temp,temp1
16
in temp1,PortD
17
andi temp1,0b11000000
18
or temp1,temp
19
out PortD,temp1
20
21
lds temp,Seg_Code1
22
lsr temp
23
andi temp,0b00000011
24
in temp1,PortB
25
andi temp1,0b11111100
26
or temp1,temp
27
out PortB,temp1
28
;*********************************
29
inc schalter
30
out PORTC, schalter
31
inc zaehler
32
rjmp anzeige_ende
33
[...]
34
35
anzeige_ende:
36
pop Temp ; das ist jetzt SREG
37
out SREG, Temp ; also SREG auf alten Stand
38
pop Temp ; und jetzt Temp wieder herstellen
39
reti
Hab jetzt nur den Teil mit anzeige1 gepostet.
Bei anzeige2 bis anzeige5 sieht es genauso aus nur mit dem Unterschied
das eben Seg_Code2 bis Seg_Code verwendet werden.
Zwischen den Sternen hatte vorher einfach nur
lds temp, Seg_Code1
out PortD, temp
gestanden.
Das Problem ist das bei deinem Programm auf PortB ebenfalls nichts
ausgegeben wird.
Ich bin aber nicht in der Lage dein Programm zu verbessern, da ich
keinen Schimmer habe was ich machen soll.
Ich bin auf eure Hilfe angewiesen.
Grüße
Jörn Christian
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
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
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.
POP Reg_A ; Zwischenspeichern, wird nochmal gebraucht
18
ANDI Reg_A, 0b01000000 ; Bit LED g ausmaskieren (0x000000)
19
SWAP Reg_A ; Bit g ist jetzt auf pos. Bit 2 (00000x00)
20
ROR Reg_A ; Position Bit 1 (000000x0)
21
ROR Reg_A ; Position Bit 0 (0000000x)
22
ANDI Reg_A, 0b00000001 ; nur die 1 auf bit 0 stehen lassen
23
In Reg_B, PInB ; Port lesen, da nur Bit 0 verändert werden soll
24
ANDI Reg_B, 0b11111110 ; Bit 0 löschen, da x auch eine 0 ergeben kann
25
Or Reg_B, Reg_A ; Reg_Bmit Reg_A verodern (xxxxxxx0 mit 0000000x)
26
Out PortB, Reg_B
27
POP Reg_A
28
ANDI Reg_A, 0b00111111 ; Jetzt die LED a-f behandeln
29
......
30
31
POP Reg_B
32
POP Reg_A
33
RET
So, den Anfang hab ich dir gezeigt. Bei der Ausgabe auf port D mußt du
ab Bit 2 mit ROL die Bits ab Bit 2 nach links schieben. NA ja, da ist
auch wieder ausmaskieren zwischenspeichern und zusammenfügen angesagt.
Nimm dir ein Blatt Papier und schreib dir auf, wie du es lösen willst.
Wenn ich jetzt noch mehr schreibe, kannst du gleich alles
zusammenkopieren und als Facharbeit abgeben....
Gruß oldmax
Ich würds schlicht Bit für Bit mit bst und bld zusammenpfriemeln. Viel
kürzer wirds mit ner wilden AND-OR-ROR-Orgie auch nicht.
1
; t enthält das Siebensegmentmuster
2
bst t, 0
3
bld pod, PD0
4
bst t, 1
5
bld pod, PD1
6
bst t, 2
7
bld pod, PD4
8
bst t, 3
9
bld pod, PD5
10
bst t, 4
11
bld pod, PD6
12
bst t, 5
13
bld pod, PD7
14
bst t, 6
15
bld pob, PB0
16
bst t, 7
17
bld pob, PB1
18
19
out PORTB, pob
20
out PORTC, poc
21
out PORTD, pod
pob, poc und pod sind fest während der gesamten Programmlaufzeit den
Ports zugeordnete Register.
>er könnte ja eigentlich schon in der Zeitzählung schon einzelne Ziffern>eintragen, dann braucht nix berechnet zu werdern.
Gut erkannt :-) Sofern man damit vom "ästhetischen Aspekt" her kein
Problem hat, spräche tatsächlich nichts dagegen, es so zu machen.
Hi
Irgendwie hatte ich gestern bezüglich der Segmentzuordnung einen Knoten
im Hirn.
Hab das nochmal überarbeitet. Das Ganze geht von folgender Zuordnung
aus:
PD7 PD6 PD5 PD4 PD3 PD2 PD1 PD0
f e d c X X b a
PB7 PB6 PB5 PB4 PB3 PB2 PB1 PB0
X X X X X X dp g
Kürzer ist es auch geworden.
1
.....
2
lds r18,segcodex ; Aufruf
3
rcall abcd ; für jede Stelle
4
out PortD,r16
5
out PortB,r17
6
....
7
8
abcd:
9
mov r16,r18 ; r16 = dp,gfedcba
10
andi r16,0b00000011 ; r16 = 000000ba
11
12
clr r17
13
lsl r18 ; r17 r18
14
rol r17 ; 0000000,dp gfedcba0
15
lsl r18
16
rol r17 ; 000000,dp,g fedcba00
17
andi r18,0b11110000 ; r18 = fedc0000
18
or r16,r18 ; r16 = fedc00ba
19
20
; Mit den Ports verknüpfen
21
in r18,PortD
22
andi r18,0b00001100
23
or r16,r18 ; Wert für PortD
24
25
in r18,PortB
26
andi r18,0b11111100
27
or r17,r18 ; Wert für PortB
28
29
ret
Falls Register gesichert werden müssen sind die 'push/pop' zu ergänzen.
MfG Spess
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
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
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
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
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
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
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
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
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