Ende einer Sprungtabelle ermitteln

Gast #2823564
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
#2823565
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.
#2823569
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!
Gast #2823570
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
Gast #2823580
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
Gast #2823614
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.
#2824318
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
Gast #2824933
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
#2825117
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
Gast #2825517
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
#2825521
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.
Gast #2825675
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
Gast #2825750
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
#2825785
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren