Forum: Mikrocontroller und Digitale Elektronik Ende einer Sprungtabelle ermitteln


von Uwe (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Uwe (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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!

von Uwe (Gast)


Lesenswert?

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

von Cube (Gast)


Lesenswert?

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".

von Uwe (Gast)


Lesenswert?

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

von Spess53 (Gast)


Lesenswert?

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

von Jürgen (Gast)


Lesenswert?

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.

von Sascha W. (sascha-w)


Lesenswert?

@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

von Uwe (Gast)


Lesenswert?

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

von Sascha W. (sascha-w)


Lesenswert?

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

von Uwe (Gast)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Sascha W. (sascha-w)


Lesenswert?

> 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

von Uwe (Gast)


Lesenswert?

Ja, die Routinen sind mit ret abgeschlossen.
Mit der Tabelle möchte ich ein Menü realisieren.

Uwe

von Sascha W. (sascha-w)


Lesenswert?

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

von Spess53 (Gast)


Lesenswert?

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

von Uwe (Gast)


Lesenswert?

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

von Spess53 (Gast)


Lesenswert?

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

von Sascha W. (sascha-w)


Lesenswert?

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

von Uwe (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.