Hallo, ich verwende in meinem Programm eine Sprungtabelle: MeinTab: RJMP Routine1 RJMP Routine2 RJMP Routine2 ... Mit folgendem Code "navigiere" ich durch die Tabelle: ldi ZL, LOW(MeinTab) ldi ZH, HIGH(MeinTab) ... adiw ZH:ZL, 1 ... Gibt es eine elegante Lösung um festzustellen, wann das Ende der Tabelle erreicht ist, z.B ohne ein Register als Zähler zu verwenden. Beim SRAM ist dies ja folgendermaßen möglich: .equ maxadress = $0070 ... LDI temp2,low (maxAdress) LDI temp3,high(maxAdress) CP temp2,XL CPC temp3,XH BRCC ENDE ... Gibt es eine Lösung für den Programmspeicher? D.h. kann ich irgendwie das Ende der Tabelle definieren? MfG Uwe
Uwe schrieb: > Beim SRAM ist dies ja folgendermaßen möglich: > > .equ maxadress = $0070 > ... > LDI temp2,low (maxAdress) > LDI temp3,high(maxAdress) > CP temp2,XL > CPC temp3,XH > BRCC ENDE > ... > > Gibt es eine Lösung für den Programmspeicher? D.h. kann ich irgendwie > das Ende der Tabelle definieren? Du kannst natürlich an das Ende der Tabelle ein Label setzen und dann mit der Adresse des Labels operieren. ... MeinTab: RJMP Routine1 RJMP Routine2 RJMP Routine2 MeinTabEnde: In Anlehung an dein SRAM Beispiel dann eben LDI temp2,low (MeinTabEnde) LDI temp3,high(MeinTabEnde) CP temp2,XL CPC temp3,XH BRCC ENDE Sieh dir doch mal in der Hilfe zum AVR-Assembler gleich im ersten Kapitel 'User's Guide' den Abschnitt 'Expressions' an. Da kann man so einiges vom Assembler berechnen lassen.
Hallo Karl Heinz! Danke!! Werd ich gleich mal ausprobieren! Ist es sicher dass der Assembler das nächste Label(MeinTabEnde) grundsätzlich in die direkt folgende Speicheradresse schreibt? Gruss Uwe
Uwe schrieb: > Hallo Karl Heinz! > > Danke!! Werd ich gleich mal ausprobieren! > Ist es sicher dass der Assembler das nächste Label(MeinTabEnde) > grundsätzlich in die direkt folgende Speicheradresse schreibt? Ich denke du meinst das richtige, auch wenn du dich schlecht ausdrückst. Denn bei einem Label wird gar nichts 'geschrieben'. Ein Label ist einfach nur ein symbolischer Name für eine bestimmte Speicheradresse. Eben die, an der der nächste Befehl im Speicher zu liegen kommt. Und ja. Der Assmbler baut da KEINE Lücken ein. Im Beispiel: MeinTab: RJMP Routine1 RJMP Routine2 RJMP Routine2 MeinTabEnde: NOP bezeichnet MeinTabEnde die nächste Speicheradresse unmittelbar nach dem letzten RJMP, also die Adresse im Speicher, an der der Befehl NOP liegt. Der Assembler legt alle Befehle immer hintereinander in den Speicher. Er darf da nicht nach Gutdünken irgendwelche Lücken lassen. (Aufpassen auf den Unterschied zwischen Byteadressierung und Wortadressierung. Aber den Unterschied kennst du ja ohnehin, weil du ihn ja beim Aufbau der Sprungtabelle auch berücksichtigen musstest. Ein Label ist eine Wortadresse. Demzufolge ergibt dann auch der Ausdruck MeinTabEnde - MeinTab die Anzahl der Einträge in deiner Sprungtabelle. Denn ein kompletter RJMP ist ja 2 Bytes - 1 Wort - groß.
1 | ldi r16, MeinTabEnde - MeinTab |
2 | |
3 | .... |
4 | |
5 | MeinTab: |
6 | RJMP Routine1 |
7 | RJMP Routine2 |
8 | RJMP Routine2 |
9 | MeinTabEnde: |
R16 wird mit dem Wert 3 geladen, weil die Tabelle aus 3 RJMP besteht. Du hättest genausogut
1 | ldi r16, 3 |
schreiben können. De facto ist das sogar dasselbe, nur eine andere Schreibweise. Nur brauch ich dir sicherlich nicht erzählen, warum die Variante 'der Assembler soll das ausrechnen' besser ist als 'ich als Programmierer zähle die RJMP'. Denn anderst du die 'Tabelle' ab auf
1 | MeinTab: |
2 | RJMP Routine1 |
3 | RJMP Routine2 |
4 | RJMP Routine2 |
5 | RJMP Routine2 |
6 | RJMP Routine2 |
7 | MeinTabEnde: |
dann wird r16 mit dem Wert 5 geladen, ohne dass du am LDI was ändern musst. Der Assembler rechnet dir aus der Differenz der beiden Adressen aus, wieviele Worte (und damit wieviele RJMP) dazwischen liegen. Lass den Assembler für dich arbeiten!
Karl Heinz schrieb: "...MeinTabEnde die nächste Speicheradresse unmittelbar nach dem letzten RJMP, also die Adresse im Speicher, an der der Befehl NOP liegt. Der Assembler legt alle Befehle immer hintereinander in den Speicher. Er darf da nicht nach Gutdünken irgendwelche Lücken lassen." Genau das meinte ich mit meiner Frage. Vielen Dank für ausführliche Erklärung! Grüsse Uwe
Karl Heinz Buchegger schrieb: > Er > darf da nicht nach Gutdünken irgendwelche Lücken lassen. Wobei es durchaus Direktiven zur Steuerung des "Layouts" gibt, z.B. "ORG".
Nachdem ich den Code ausprobiert habe ist mir etwas Seltsames aufgefallen! Solange sich die Sprungtabelle am Ende der des Quelltextes (ca 700 Zeilen) befand, hat der BRCC Sprung nicht funktioniert: ... LDI temp2,low (MeinTabEnde) LDI temp3,high(MeinTabEnde) CP temp2,XL CPC temp3,XH BRCC ENDE ... Erst als ich die Tabelle in unmittelbare Nähe (genau genommen über die o.g Subroutine) verschoben habe, hat der Code funktioniert. Verhält es sich hier wie mit mit dem brsh-Befehl (nur maximal 63 Worte weit springen). Oder evtl. buggy?? Mfg Uwe
Hi >Verhält es >sich hier wie mit mit dem brsh-Befehl (nur maximal 63 Worte weit >springen). Oder evtl. buggy?? Alle bedingte relative Sprünge haben diese Beschränkung. MfG Spess
Uwe schrieb: > ich verwende in meinem Programm eine Sprungtabelle: > > MeinTab: > RJMP Routine1 > RJMP Routine2 > RJMP Routine2 > ... > > Mit folgendem Code "navigiere" ich durch die Tabelle: > > ldi ZL, LOW(MeinTab) > ldi ZH, HIGH(MeinTab) > ... > adiw ZH:ZL, 1 > ... > > Gibt es eine elegante Lösung um festzustellen, wann das Ende der Tabelle > erreicht ist, z.B ohne ein Register als Zähler zu verwenden. Außer die Endadresse der Tabelle zu vergleichen, gibt es auch noch die Möglichkeit, ans Ende der Tab eine Null anzuhängen. Diese kann dann einfach mit tst überprüft werden. Oder - Als letzten Tabelleneintrag rjmp ResetTab, welcher deinen Tabellenzeiger zurücksetzt. Welche Möglichkeit für dich am besten ist, hängt natürlich vom weiteren Aufbau deines Programms ab.
@Uwe, was ich nicht verstehe - warum schreibst du Sprungbefehle in die Sprungtabelle? So wie du es jetzt hast, springst du mit ijmp in die Tabelle und dort gehts weiter zum eigentlichen Ziel. Mach es so, das in der Tabelle nur die Zieladressen stehen, denn mit deinen jrmp in der Tabelle wirst du auf Dauer auch nicht weit kommen. Bsp:
1 | MeinTab: .dw Routine1,Routine2,Routine2,0 |
2 | |
3 | |
4 | Nav: |
5 | ldi zl,low(MeineTab*2) |
6 | ldi zh,high(MeineTab*2) |
7 | |
8 | ;zum nächsten Element |
9 | adiw Z,2 |
10 | |
11 | ;Element holen |
12 | lpm xl,Z+ |
13 | lpm xh,Z+ ;Sprungziel laden |
14 | mov r16,xl |
15 | or r16,xh ;Ziel 0x0000? -> ENDE |
16 | breq tab_Overflow |
17 | ;Element aufrufen |
18 | movw z,x |
19 | ijmp |
das Ende in dieser Form an die Tabelle anzuhängen, bringt natürlich nur was, wenn mann die Elemente nacheinander abruft. Es nützt nichts, wenn du 3 Sprungziele hast und versuchst das Ziel 5 abzurufen. Sascha
Danke Sascha für die Alternative, aber ich will es erstmal mit der ersten Variante versuchen! Im Moment hab ich folgendes Problem (ich hab den Code abgeändert bzw. stark vereinfacht um das Problem besser beschreiben zu können): main: rcall TabDurchlaufen rjmp main TabDurchlaufen: push temp2 push temp3 adiw ZH:ZL, 1 LDI temp2,low (MeineTabEnde) LDI temp3,high(MeineTabEnde) CP temp2,ZL CPC temp3,ZH BRCC ENDE ldi ZL, LOW(MeinTab) ldi ZH, HIGH(MeinTab) loop: ldi temp1, 'k' ;Text am LCD ausgeben wenn TabEnde erreicht rcall lcd_data ;LCD Ausgabe rjmp loop ENDE: pop temp3 pop temp2 ret MeineTab: RJMP Routine1 RJMP Routine2 RJMP Routine3 RJMP Routine4 MeineTabEnde: Funktioniert so perfekt, die Tabelle wird durchlaufen und das Programnm endet in der "loop"-Schleifen bzw. Text wird am LCD ausgegeben. Sobald ich jedoch den "icall"-Befehl hinzufüge, um die einzelnen Routinen aufzurufen, wird das Tabellenende nicht mehr erkannt, d.h. das Programm endet nicht in der "loop"-Schleife. ... main: rcall TabDurchlaufen icall ;diesmal mit icall rjmp main ... Hat vielleicht jemand eine Idee? MfG Uwe
Hallo, sind die Routinen auch mit ret abgeschlossen? Wird zwischen "TabDurchlaufen" und icall noch was anderes gemacht was dir Z verändert? Wird Z innerhalb der Routinen verändert? Es währe es sowieso besser am Ende von "TabDurchlaufen" kein ret, sondern ein ijmp zu setzen, das ret aus der Routine führt dann direkt zurück zu Main. Sascha
Hi, das Z Register wird durch die Routinen definitiv nicht verändert. Hab testweise den kompletten Code aus den Routinen gelöscht um sicherzugehen. Ich hab es auch mal mit ijmp versucht. Gleiches Resultat. Sobald ich ijmp oder icall aufrufe, wird das Ende der Tabelle nicht erkannt. Grüsse Uwe
Uwe schrieb: > Hi, > > das Z Register wird durch die Routinen definitiv nicht verändert. Hab > testweise den kompletten Code aus den Routinen gelöscht um > sicherzugehen. > > Ich hab es auch mal mit ijmp versucht. Gleiches Resultat. Sobald ich > ijmp oder icall aufrufe, wird das Ende der Tabelle nicht erkannt. Und was ergibt ein Single Step durch den Code? Im Simulator vom AVR-Studio hast du doch eine Anzeige aller Register und wie sie sich von Schritt zu Schritt verändern. Geh halt mal den Teil in Einzelschritten durch (F11) und sieh dir an, was die Register machen, was am Stack liegt etc. etc.
> Uwe schrieb: >> Hi, >> Ich hab es auch mal mit ijmp versucht. Gleiches Resultat. na was denn nun - sind die Routinen die du aufrufst mit ret abgeschlossen oder nicht!? Wenn ja hat ijmp ja keinen Sinn, wenn nein was passiert dann am Ende der Routinen. Was willst du überhaupt mit dem Program und diesem Aufbau bezwecken? Sascha
Ja, die Routinen sind mit ret abgeschlossen. Mit der Tabelle möchte ich ein Menü realisieren. Uwe
Uwe schrieb: > Im Moment hab ich folgendes Problem (ich hab den Code abgeändert bzw. > stark vereinfacht um das Problem besser beschreiben zu können): vielleicht solltest du doch wieder etwas mehr Code zeigen um das Problem besser lösen zu können. Sascha
Hi Mit ein paar Korrekturen ist dein Programm auch mit 'ijmp' lauffähig:
1 | .include "m8def.inc" |
2 | |
3 | RESET: |
4 | ldi r16,high(RAMEND) |
5 | out SPH,r16 |
6 | ldi r16,Low(RAMEND) |
7 | out SPL,r16 |
8 | |
9 | LDI ZL,low (MeineTab-1) |
10 | LDI ZH,high(MeineTab-1) |
11 | |
12 | main: |
13 | rcall TabDurchlaufen |
14 | rjmp main |
15 | |
16 | TabDurchlaufen: |
17 | push r17 |
18 | push r18 |
19 | adiw ZH:ZL, 1 |
20 | LDI r17,low (MeineTabEnde) |
21 | LDI r18,high(MeineTabEnde) |
22 | CP ZL,r17 |
23 | CPC ZH,r18 |
24 | BRCC ENDE |
25 | |
26 | icall |
27 | rjmp return |
28 | |
29 | ende: |
30 | LDI ZL,low (MeineTab-1) |
31 | LDI ZH,high(MeineTab-1) |
32 | |
33 | return: |
34 | pop r18 |
35 | pop r17 |
36 | ret |
37 | |
38 | MeineTab: |
39 | RJMP Routine1 |
40 | RJMP Routine2 |
41 | RJMP Routine3 |
42 | RJMP Routine4 |
43 | MeineTabEnde: |
44 | |
45 | Routine1: ret |
46 | Routine2: ret |
47 | Routine3: ret |
48 | Routine4: ret |
MfG Spess
Danke! Werde ich gleich mal ausprobieren. Mir ist in der AVR Studio Simulation aufgefallen, dass die Carry Flag nicht gesetzt wird, auch wenn die Registerpaare temp2,temp3 und ZL,ZH die gleichen Werte haben. Also der Sprung Brcc wird jedesmal ausgeführt, da die Carry Flag nie gesetzt wird! ... LDI temp2,low (MeineTabEnde) LDI temp3,high(MeineTabEnde) CP temp2,ZL CPC temp3,ZH BRCC ENDE ... Gruss Uwe
Hi >Also der Sprung Brcc wird jedesmal ausgeführt, >da die Carry Flag nie gesetzt wird! Deswegen habe ich die Vergleiche auch umgedreht. MfG Spess
Uwe schrieb: > Mir ist in der AVR Studio Simulation aufgefallen, dass die Carry Flag > nicht gesetzt wird, auch wenn die Registerpaare temp2,temp3 und ZL,ZH > die gleichen Werte haben. was anderes ist auch nicht zu erwarten: Gleichheit der Werte erzeugt nun mal keinen Übertrag, der entsteht erst, wenn der zweite Operand größer als der erste ist (bei cp, sub, ...). Sascha
Danke! hat auf Anhieb funktioniert! Mit Vergleich umgedreht meinst du: CP ZL,r17 CPC ZH,r18 statt CP r17, ZL CPC r18, ZH Den Fehler hatte ich glatt übersehen. Danke Nochmals!!! Grüsse Uwe
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.