Hallo zusammen,
ich habe in Assembler für den ATMEGA 8 testweise mal ein Programm
entworfen, das auf einem LCD Display ein Menü darstellen soll.
Leider funktiniert das ganze noch nicht wie es soll. Wie es aussieht
wird das Programm immer wieder durchlaufen, ich sehe auf dem LCD nur
filmmerndes Schwarz.
Kann mir vielleicht jemand meinen Fehler nennen?
Beim Drücken einer bestimmten Taste wird ein Interrupt ausgelöst, das
Register r20 wird je nach Taste erhöht oder erniedrigt.
Diese Zahl im Register wird im Z-Pointer zu der Startadresse einer
Sprungtabelle addiert und somit kann ich mit dem Befehl icall direkt
dorthin springen. Mit dem nun angesprungenen Programm gebe ich dann auf
dem Bildschirm den Name des Menüs auf dem Display aus.
Vielen Dank schonmal im Voraus für eure Hilfe
gruß Philipp
Hallo,
hab nur kurz rübergeschaut:
add ZL,r20
push ZH
push ZL
wer garantiert Dir, da0 es keinen Übertrag ins H-Byte gibt?
clr r19 ; oder was frei ist...
add ZL,r20
adc ZH,r19
weiter: Du weißt, daß Tasteb prellen?
Wenn Du da einmal drückst gibt es vermutlich ein Dutzend Interrupts
hintereinander...
Stichwort: Tasten entprellen
Gruß aus Berlin
Michael
Danke für deine Tips.
Mit dem Übertrag hast du Recht, daran habe ich gar nicht gedacht, dass
wenn die Sprungtabelle eine höhere Z-Pointer Adresse hat eventuell ein
Übertrag entstehen kann.
Auf das Tasten entprellen verzichte ich, da ich mit dem Prellen noch nie
probleme hatte. Habe vorher schon ein einfaches Programm geschrieben,
mit dem man mit 2 Tasten entweder eine Zahl hoch oder runterzählen
konnte, das hat auch wunderbar ohne entprellen geklappt.
Mein eigentliches Problem bleibt aber leider nach wie vor bestehen. Wäre
nett wenn jemand mal drüberschauen könnte.
Noch ein kleiner Nachtrag: Jetzt ist die Schrift auf dem Display schon
viel deutlicher zu lesen, offensichtlich werden jetzt in einer
Endlosschleife alle 3 Menünamen hintereinander ausgegeben.
Bei der Simulation mit dem AVR Studio ist mir aufgefallen, dass mit dem
Befehl reti in der Warteschleife zur Ausgabe des 2. Menüpunktes
gesprungen wird. Ich habe eigentlich gedacht, reti wäre dazu da, um
Interrupts wieder freizugeben, ist dem nicht so?
>weiter: Du weißt, daß Tasteb prellen?>Wenn Du da einmal drückst gibt es vermutlich ein Dutzend Interrupts>hintereinander...>Auf das Tasten entprellen verzichte ich, da ich mit dem Prellen noch nie>probleme hatte.>offensichtlich werden jetzt in einer>Endlosschleife alle 3 Menünamen hintereinander ausgegeben.
Na dann...
>Bei der Simulation mit dem AVR Studio ist mir aufgefallen, dass mit dem>Befehl reti in der Warteschleife zur Ausgabe des 2. Menüpunktes>gesprungen wird.
reti sollte dahin zurückspringen, wo das Programm beim Auslösen des
Interrupts gerade war. Tut es das nicht, hast du irgendwo zwischendrin
den Stack versemmelt.
>Ich habe eigentlich gedacht, reti wäre dazu da, um>Interrupts wieder freizugeben, ist dem nicht so?
Doch. Reti setzt das I-Flag.
Oliver
Philipp schrieb:
> Auf das Tasten entprellen verzichte ich, da ich mit dem Prellen noch nie> probleme hatte.
Jetzt halten sich 99% der Leser vor Lachen den Bauch.
Du wirst schon merken, daß diese Einstellung grundfalsch ist, wenn Deine
Programme erstmal größer werden.
Du hast warscheinlich entprellt, ohne es zu wissen, durch lange Delays
(z.B. LCD-Clear).
Aber Delays fressen Dir kostbare CPU-Zeit auf und die fehlt dann in
größeren Projekten.
Peter
Wenn ich mir dein Programm so ansehe, denke ich, dass dir der
Unterschied zwischen einem Sprung (JUMP) und einem Unterprogrammaufruf
(CALL) nicht klar ist.
Mit einem Jump geht die Programmausführung ganz einfach an einer anderen
Stelle weiter. Mehr passiert nicht.
Mit einem Call hingegen ist die Situation eine andere. Wenn der Call
ausgeführt wird, wird die momentane Programmadresse auf dem Stack
abgelegt. Die Programmausführung geht dann an der im Call angegebenen
Stelle weiter. Und zwar solange, bis ein Ret (Return from Call)
angetroffen wird. Dann wird die im Stack zwischengespeicherte Adresse
geholt und die Programmausführung geht dann unmittelbar nach dem
aufrufenden Call weiter.
Bei einem Call wird also abgespeichert, von wo der Call gekommen ist und
mit einem Ret kommt man an genau diese Stelle wieder zurück.
Weiters ist es wichtig, dass
* der Stack bei vielen Calls nicht übergeht. Also nicht zuviel am Stack
zwischengespeichert wird
* Alles was nach dem Call zb durch Push auf den Stack geschoben wird,
vor dem Ret per Pop wieder vom Stack entfernt wird.
Der µC führt nicht Buch, wo am Stack die beim Call gespeicherte
Returnadresse liegt. Der holt sich einfach vom Stack die nächsten beiden
Bytes (oder wie lange eine Adresse im System ist) und benutzt die als
Returnadresse.
Und nein. Der Hauptzweck eines reti ist es, aus dem aufgerufenen
Unterprogramm wieder zur Aufrufstelle zurückzukommen. So wie das ein
normaler ret auch macht. Nur werden zusätzlich die Interrupts wieder
freiggeben.
Warum ich auf den Gedanken komme, dass dir der Unterschied zwischen jump
und call nicht klar ist. Zb. Hier:
hier macht der call absolut keinen Sinn. Du willst hier nicht ein
'Unterprogramm' namens .... aufrufen. DU willst einfach nur, dass die
Programmausführung bei .... weiter geht.
Sorry. War nicht angemeldet und wollte die Details im vorhergehenden
Post noch ergänzen.
Also nochmal:
Warum ich auf den Gedanken komme, dass dir der Unterschied zwischen jump
und call nicht klar ist. Zb. Hier:
1
rProg3:
2
lpm temp1, Z+
3
cpi temp1,0
4
breq warte
5
rcall lcd_data
6
rcall rProg3
hier macht der call absolut keinen Sinn. Du willst hier nicht ein
'Unterprogramm' namens rProg3 aufrufen. Du willst einfach nur, dass die
Programmausführung bei rProg3 weiter geht, weil du eine Schleife formen
willst.
1
rProg3:
2
lpm temp1, Z+
3
cpi temp1,0
4
breq warte
5
rcall lcd_data
6
rjmp rProg3
an dieser Stelle war der rcall völlig kontraproduktiv. Bei jedem rcall
wurde wieder eine Rücksprungadresse auf dem Stack abgelegt.
Es gibt noch mehr solcher Stellen in deinem Code.
Vielen Dank für deine Hilfe. Du hattest natürlich Recht, der Unterschied
zu "call" und "jump" war mir nicht so ganz bewusst. Darauf habe ich gar
nicht geachtet. Jetzt wo du es sagst wird mir mein Fehler natürlich
klar.
Nachdem ich meinen Fehler verbessert habe, funktioniert das Programm
auch fast.
Ein Problem habe ich allerdings noch. Wie du gesagt hast springt "reti"
zurück an den Punkt im Programm an dem das Interrupt. Ich möchte aber
einfach nur die Interrupts wieder freigeben Dazu habe ich einfach mal
"sei" genommen, obwohl im Tutorial steht, dass das nur zum generellen
Interrupt freigeben am Anfang wäre. Nun wird aber überhaupt nicht mehr
auf ein Tastendruck reagiert.
Was habe ich jetzt schon wieder falsch gemacht?
Ich fände es eigentlich besser, "reti" zu verwenden, aber damit springt
er an den Anfang des Programmes. Eigentlich sollte er doch an die
Stelle, an der das Interrupt aufgetreten ist, springen, also in die
Warteschleife???
Philipp schrieb:
> Ein Problem habe ich allerdings noch. Wie du gesagt hast springt "reti"> zurück an den Punkt im Programm an dem das Interrupt. Ich möchte aber> einfach nur die Interrupts wieder freigeben
Ich kann dir nur raten nach den Regeln zu spielen.
Ein Interrupt löst einen call aus. Ist die Abarbeitung des Interrupts
abgeschlossen, wird die ISR mit einem reti beendet.
Natürlich kann man das auch anders machen, wenn man weiß was man tut und
selbst auf dem Stack aufräumt. Aber empfehlenswert ist es am Anfang
nicht das zu tun.
Wenn man nach den Regeln spielt, hat man auf lange Sicht immer die
wenigsten Probleme.
> Ich fände es eigentlich besser, "reti" zu verwenden, aber damit springt> er an den Anfang des Programmes.
Ich würde mal dein Programm kräftig umstellen.
Du springst in deinem Programm hin und her. Bei manchen Sachen hab ich
den Eindruck, die sind nur deshalb gerade an dieser Stelle im Code, weil
der Cursor beim Tippen genau dort stand :-)
Verusch deinen Code so zu strukturieren, dass die Hauptarbeitsrichtung
immer 'nach unten' ist. Versuch auch Sprünge (mit Ausnahme von
Schleifenkonstrukten) nach Möglichkeit 'nach unten im Code' führen.
Meistens ist es dann leichter den Programmfluss zu verfolgen.
Du Springst bei einem Interrupt von Int0 oder Int1 zu Erhöhen oder
Erniedrigen.
Als erstes solltest du mal die Interruptroutinen nicht mitten in das
Hauptprogramm reinschreiben. Ich schreibe sie ganz an den Anfang des
Programmes. So wird das ganze übersichtlicher.
Dann springst du aus "Erhöhen" / "Erniedrigen" nach "Pointer".
Da das der Programmanfang ist, springt er eben immer zum Anfang.
Ersetze mal das "rjmp Pointer" durch "reti", dann macht er an der Stelle
im Programm weiter, wo der Interrupt auftrat.
Du musst dann unter "bleib:" auswerten, ob eine Taste gedrückt wurde
oder nicht.
Ich habe auch gerad ein Menu programmiert, dort lasse ich das Programm
immer wieder schauen, welcher Screen ausgegeben werden muss. Wenn
aktuell auszugebende Screen schon beim letzten mal ausgegeben wurde,
wird er nicht ausgegeben, also die Datenübertragung ans LCD wird einfach
übersprungen.
Gruss Stefan
Edit: Da war wohl schon jemand schneller mit einer guten Idee^^
Auf die Idee, immer wieder die Zählervariable auszulesen bin ich auch
schon gekommen. Ich habe mir aber gedacht, dass das ziemlich viel
Rechenleistung benötigt. Wenn man das alles in einem Interrupt löst,
wird es ja pro Tastendruck nur einmal durchlaufen.
Hi
>Auf die Idee, immer wieder die Zählervariable auszulesen bin ich auch>schon gekommen. Ich habe mir aber gedacht, dass das ziemlich viel>Rechenleistung benötigt.
Ob dein Controller in der Warteschleife hängt, oder etwas Sinnvolles
macht kommt auf das gleiche heraus.
MfG Spess
Philipp schrieb:
> Auf die Idee, immer wieder die Zählervariable auszulesen bin ich auch> schon gekommen. Ich habe mir aber gedacht, dass das ziemlich viel> Rechenleistung benötigt. Wenn man das alles in einem Interrupt löst,> wird es ja pro Tastendruck nur einmal durchlaufen.
Alte Regel:
Machs erst korrekt.
Dann sieh nach ob du ein Zeitproblem hast.
Dann machs schneller, wenn du ein Zeitproblem hast
Sieh dir mal Folgendes an.
Eine Sammlung von Unterprogrammen die aufgerufen werden und bei denen
jede eine eindeutige, einzige und dokumentierte Funktion ausführt.
Von jedem Unterprogramm kann man sich leicht überzeugen, dass es das
Gewünschte macht. Kein wildes Ge-jumpe quer durch den Gemüsegarten, in
der Hoffnung, das der ret auf den man irgendwann stossen wird, schon das
richtige vom Stack holen wird.
Das kann man dann auch im Simulator durchsteppen, ohne nach 3
jumps/calls den Überblick zu verlieren, wo man denn her gekommen ist,
warum der jump jetzt genau dorthin geht etc.
Gerade in Assembler ist Organisation das halbe Leben. Und ja: dazu
gehört auch eine etwas ansprechende Source Code Gestaltung. In so einer
Assemblerwurst, die sich über 2 Seiten hinzieht, und bei der man nicht
weiß wo man mit dem Stochern anfangen soll, findet sich kein Mensch
zurecht. Auch du nicht, nachdem 2 Wochen ins Land gezogen sind.
Ich habe mein Programm mal etwas verbessert, inklusive Struktur.
Ich würde es aber trotzdem gerne so lassen, dass ich die komplette
Bildschirmausgabe im Interrupt ausführe, denn das Menü ist wie gesagt
nur Nebensache, die Hauptprozessorkapazität soll für andere
Unterprogramme sein.
Die Warteschleife habe ich nur als Platzhalter für andere Programme
geschrieben.
Hoffentlich ist der Quelltext jetzt etwas anschaulicher, es funktioniert
schon besser, leider komme ich mit den Tastendrück nicht auf die
nächsten Menünamen, sondern es stehen immer komische Buchstaben da.
Findet vielleicht jemand den (jetzt nur noch kleinen) Fehler?
Philipp schrieb:
> Ich habe mein Programm mal etwas verbessert, inklusive Struktur.
Viel ist davon nicht zu erkennen.
> Ich würde es aber trotzdem gerne so lassen, dass ich die komplette> Bildschirmausgabe im Interrupt ausführe, denn das Menü ist wie gesagt> nur Nebensache, die Hauptprozessorkapazität soll für andere> Unterprogramme sein.
Ja, und.
Das Beispiel das ich dir gegeben habe funktioniert noch immer genauso
wie du es dir vorgestellt hast: Menüausgabe im Interrupt
> Findet vielleicht jemand den (jetzt nur noch kleinen) Fehler?
Ich mach mir jetzt nicht nochmal die Mühe und analysiere dein Programm.
Stopf es in den Simulator rein und geh mit Einzelschritten durch was bei
einem Interrupt passiert.
Hi
>ch habe mein Programm mal etwas verbessert, inklusive Struktur.
Nicht verschlimmert?
>brie bleib ; wenn nicht durch Interrupt ausgelöst dann zu bleib
Du willst bestimmt nicht wissen, welche Haare sich sich da alle bei mir
stäuben.
Nimm mir es nicht übel, das ist kein Programm, das ist eine Katastrophe!
Das überhaupt etwas geht, hast du nur ein paar glücklichen Umständen zu
verdanken.
>Hoffentlich ist der Quelltext jetzt etwas anschaulicher
Nein. Sieh dir mal das Programm von Karl Heinz an. Wenn sich schon
jemand, der 100mal mehr vom Programmieren versteht wie du, sich die Mühe
macht, dir ein funktionierendes Programm zu schreiben, solltest du das
auch annehmen.
MfG Spess
Philipp schrieb:
> Ich habe mein Programm mal etwas verbessert, inklusive Struktur.
Och nö.
Da hat sich der Karl Hein soviel Mühe gegeben und alles war umsonst.
Eine Sache gibts aber auszusetzen: