Forum: Mikrocontroller und Digitale Elektronik alternativ zu switch / case


von Siegfried S. (dieleena)


Lesenswert?

hallo,

aus eine Tabelle lese ich bis zu 255 8Bit Werte, die ich dann in einer 
switch / case Anweisung weiter verarbeite. Da der Befehlscode 
(Assembler) von switch immer größer wird, je mehr case Anweisungen 
existieren, suche ich hierfür eine alternative.

Gruß Siegfried

von Phantomix (Gast)


Lesenswert?

Hängt davon ab was bei den einzelnen Werten passieren soll. Wenn du 
wirklich 255 unterschiedliche Fälle hast, ist - vom Speicherbedarf 
gesehen - die switch dein kleinstes Problem.
Was steht denn in den einzelnen cases deiner Switch drin?

von Karl H. (kbuchegg)


Lesenswert?


von Siegfried S. (dieleena)


Lesenswert?

Hallo,
Die Anweisung der einzelnen cases meiner Switch ist sehr 
unterschiedlich.
Habe mal im Disassembly nachgerechnet. jede nachfolgende Case Anweisung 
benötigt beim C30 Compiler ca 8 bis 10 * 2 Byte.

jenachdem, welche case Anweisung bearbeitet werden soll, ergibt auch 
eine unterschiedliche Laufzeit,

Gruß

von manateemoo (Gast)


Lesenswert?

Wenn es um Tempo geht machst du am besten ein Array mit 255 
Funktionspointern.

von woko (Gast)


Lesenswert?

bei den PICs gibt es diesen Trick. Den Wert des Switch steht im W 
Register. Der wird zum Programmcounter hinzugezählt (ADDWF). DT 
(Data-Table o. ä.) gibt dann den Wert ins W Register und springt ans 
Ende der Tabelle.

HTH,
Wolfgang

Beispiel:

TABLE_H_CW
    addwf   PCL, F
    dt 0x0
    dt 0x0
    dt 0x1
    dt 0x2
    dt 0x3
    dt 0x3
    dt 0x4
    dt 0x5
    dt 0x5
    dt 0x6
    dt 0x6
    dt 0x6
    dt 0x7

von Siegfried S. (dieleena)


Lesenswert?

Hallo,
vielen Dank für die Hilfe.
Werde alles in einem Array ablegen.
Muß nur noch sehen, wie ich einen "unbekannten" Befehl abfangen kann.
Gruß Siegfried

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Siegfried Saueressig schrieb:
> Muß nur noch sehen, wie ich einen "unbekannten" Befehl abfangen kann.

Mit einem Pointer auf eine allgemeine "unbekannt"-Routine. Der wird an 
allen Stellen ins Array eingetragen, für die der Array-Index keine 
definierte Bedeutung hat.

von Peter D. (peda)


Lesenswert?

Solche Klimmzüge sind unnötig.
Ein optimierender Compiler erkennt selber, ab wann eine Tabelle 
günstiger ist

Schreib einfach mal 20 aufeinanderfolgende Case, die verschiedenes 
machen und guck ins Listing, dann sollte da ne Sprungtabelle stehen.


Peter

von Paul (Gast)


Lesenswert?

>bei den PICs gibt es diesen Trick. Den Wert des Switch steht im W
>Register. Der wird zum Programmcounter hinzugezählt (ADDWF). DT
>(Data-Table o. ä.) gibt dann den Wert ins W Register und springt ans
>Ende der Tabelle.

Vorsicht: Je nach Paging erkennt diese Routine den Pagerand nicht, der 
addwf PC, F führt dann zum Überrollen.

Beim PIC braucht man die Werte nicht mal mit dt einzubinden. Dafür gibt 
es den Befehl retlw:

TABLE_H_CW
    addwf   PCL, F
    retlw 0x0
    retlw 0x0
    retlw 0x1

>Muß nur noch sehen, wie ich einen "unbekannten" Befehl abfangen kann.
>Gruß Siegfried

Mit orwf vor der addwf-routine.

In C kannst Du switch case mit else if ersetzen. Je nach Compiler kann 
das zu besseren Ergebnissen führen.

if (....)
{ ...

}
else if(...)
{
...
}
else if(...)
{
...
}
else (...)
{
...
}

von Siegfried S. (dieleena)


Lesenswert?

MPLAP 8.40  C30 3.21
Hallo,

@ Peter Dannegger
leider keine Tabelle gefunden.

Habe eine Tabelle mit 256 Einträge im Flash angelegt. Entsprechend auch 
notwendige Unterprogramme. Ist etwas Arbeit, aber nicht schlimm.

call Aufruf
1
215:      p_ablauf_list[table_codex]();
2
 06416  BFD64E     mov.b 0x164e,0x0000
3
 06418  FB8000     ze 0x0000,0x0000
4
 0641A  400080     add.w 0x0000,0x0000,0x0002
5
 0641C  2CD720     mov.w #0xcd72,0x0000
6
 0641E  408000     add.w 0x0002,0x0000,0x0000
7
 06420  780010     mov.w [0x0000],0x0000
8
 06422  010000     call 0x0000  
9
         =^  ca 30 Byte

das Unterprogramm
1
379:               {
2
 065E2  FA0000     lnk #0x0
3
//
4
//  ---->>>>  hier der Code im Unterprogramm
5
6
392:               return(0);
7
 0660E  EB0000     clr.w 0x0000
8
393:               }
9
 06610  FA8000     ulnk
10
 06612  060000     return              =^  ca 10 Byte

wenn ich die switch Routine ansehe, komme ich schnell 100 Byte und mehr.

am liebsten wäre es mir, wenn ich eine Sprung-Make in eine Tabelle 
bekommen würde. Somit wären die Unterprogramm Code nicht notwendig.
Wäre gut, wenn man dabei hilft.

Gruß Siegfried

von Siegfried S. (dieleena)


Lesenswert?

MPLAP 8.40  C30 3.21

Hallo,
gibt es noch eine bessere Lösung als switch / case bzw. Tabelle mit
Zeiger(call) ?
wenn ich richtg gerechnet habe, benötig das Programm ca 21 Takt mit der 
Tabelle Lösung

Gruß Siegfried

von oldmax (Gast)


Lesenswert?

Hi
Könnte mir vorstellen, das folgendes bei einem Atmega8 funktioniert....
Im Programm hast du ein paar Sprungmarken, 255 sozusagen...
1
marke_0: RJMP Prog_0
2
marke_1: RJMP Prog_1
3
marke_2: RJMP Prog_2
4
marke_3: RJMP Prog_3
5
marke_4: RJMP Prog_4
6
...
nun hast du eine Tabelle, deren Inhalt 255 Werte enthält....
1
LDI  XL,LOW(Tabelle)  ; Pointer besetzen     
2
LDI  XH,HIGH(Tabelle)  
3
ADD  XL, Tab_Pos  ; aktuellen Zeigerwert addieren
4
ADC  XH, Zero
5
LD       Temp_Reg_A, X      ; diesen Wer mit 2 multiplizieren
6
CLR      Tem_Reg_B
7
LSL   Temp_Reg_A  
8
rol   Temp_Reg_B 
9
LDI  XL,LOW(Marke_0)  ; Pointer besetzen     
10
LDI  XH,HIGH(Marke_0)  
11
ADD  XL, Temp_Reg_A  ; aktuellen Zeigerwert addieren
12
ADC  XH, Temp_Reg_B
13
Push     XH                 ; evtl. High und Low tauschen...
14
Push     XL
15
RET                         ; sollte dann in der Sprungtabelle landen
Also, grob beschrieben...
Über das Z-Register adressierst du die Tabelle und lädst dir den Wert 
heraus, multiplizierst mit 2, lädst die Spungmarke in ein 
Doppelregister, addierst den angepassten Wert aus der Tabelle und Pusht 
diesen Wert auf den Stack. Anschließend leitest du einen Rücksprung mit 
"RET" ein. Der Controller sollte dann bei irgen deiner Marke landen und 
dort das gewünschte Programm aufrufen. Allerdings ist dies nur eine 
Überlegung, ich hab sowas noch nicht gemacht und kanns hier nicht 
prüfen. Sollte aber von der Überlegung her funktionieren.
Gruß oldmax

von Siegfried S. (dieleena)


Lesenswert?

Hallo,
@ oldmax
Danke für deine Antwort. Ich programmier unter "C" mit dem C30 Compiler.
Wie man dort Assembler einbindet, ist mir nicht bekannt.
"Sprungmarken" in einer Tabelle wäre eine Alternative, aber wie ?
Gruß Siegfried

von Ulrich P. (uprinz)


Lesenswert?

Wenn die Tabelle immer vollständig ist, also keine Lücken aufweist, die 
über einen default-Handler behandelt werden müssen, dann kannst Du die 
Tabelle so ausführen, dass sie einem vielfachen einer 2er Potenz 
entspricht.
Dann wandelst Du das Byte in dem Du einen Left-Shift machst und addierst 
die Anfangsadresse der Tabelle dazu und springst.
Braucht das 21 Cycles? Wäre ein weiterer Grund nicht mit PIC zu arbeiten 
:)

Ulrich

von Siegfried S. (dieleena)


Lesenswert?

Hallo,
@ Ulrich
wäre dankbar, wenn du deine vorgehenweise mir erklärst. Habe dieses so 
noch nicht gemacht.


derzeitig arbeite ich mit Unterprogramme. Die Adressen stehen in einer 
Tabelle.

hier kleinen Ausschnitt aus meinem Programm

CALL Tabelle:
1
P_Ptr_Auftrag const p_Auftrag_list[] = 
2
  { 
3
//
4
  auftrag_FALSE,     //  C_FALSE,  //  000
5
  auftrag_TRUE,     //  C_TRUE,  //  001
6
  auftrag_INPUT,     //  C_Input,  //  002
7
  auftrag_OUTPUT,     //  C_Output,  //  003
8
  auftrag_DEFAULT,     //  C_GLEIS_LANGE,//  004
9
  auftrag_TIMER,     //  C_TIMER,//  005
10
  ......
11
    insgesamt 256 Einträge
12
  }

Hauptprogramm:
Der Code für die Auswahl des Unterprogramm, steht in einem großem 
Speicher.
1
for (schleife_auswahl = von_auswahl_addr; schleife_auswahl < bis_auswahl_addr; schleife_auswahl ++)
2
{
3
p_auswahl_list[memory_codex[schleife_auswahl]]();


Unterprogramme:
1
P_Auswahl auswahl_FALSE (void)  //  000
2
{
3
fs.exit_break = 0;
4
return(0);
5
}
6
7
   ..... n Unterprogramme
8
9
P_Auswahl auswahl_TRUE (void)  //  001
10
{
11
fs.exit_break = 0;
12
return(0);
13
}


Gruß Siegfried

von oldmax (Gast)


Lesenswert?

Hi
Leider kann ich dir bei C nicht helfen und den von dir verwendeten µC 
kenne ich auch nicht. Allerdings schreibst du, das du die Adressen der 
Unterprogramme aus einer Tabelle aufrufst, nichts anderes sollte der von 
mir geschriebene Code machen. Wenn du weißt, wie du eine Adresse 
berechnen und anspringen kannst, dürfte die Umsetzung doch kein Problem 
mehr sein. Wichtig zu wissen, wie die Befehle adressiert sind. Beim 
Atmega8 hat jeder Befehl eine Breite von 16 Bit, also reicht es, wenn 
der Programmcounter nur gerade Adressen aufruft. Somit ist das 0-Bit 
irrelevant. Deshalb die Multiplikation mit 2, bzw. die Schiebeoperation. 
In C braucht dich dies doch aber nicht zu kümmern. Du mußt eben nur 
sehen, wie du die entsprechenden Adressen berechnen mußt.
Gruß oldmax

von Ulrich P. (uprinz)


Lesenswert?

Hi!

Es war mir aus den vorangegangenen Aussagen nicht klar, ob Du in 
Assembler oder C arbeitest.

Aber meine idee lässt sich in beiden Sprachen umsetzen.
1
typedef void(*fkt)(void);
2
3
const fkt FktTabelle[255] = {
4
   ... Liste mit Funktionen ...
5
};
6
7
void sprung( uint8_t wert)
8
{
9
  fkt *myFkt = &FktTabelle[wert]; /* Funktion zum Anspringen */
10
  myFkt();
11
}

Das war jetzt minimalistisch. Das kann man auch erweitern:
1
typedef int(*fkt)(uint8_t);
2
...
3
4
int sprung( uint8_t wert)
5
{
6
  fkt *myFkt = &FktTabelle[wert]; /* Funktion zum Anspringen */
7
  return myFkt(wert);
8
}

C hat aber den Nachteil, dass jede Funktion erst einmal einen Function 
Header durchläuft in dem einige Register auf dem Stack gesichert werden. 
Nach dem Durchlaufen der Funktion werden diese wieder zurück geholt.
Das benötigt Zeit und ist in diesem Fall überflüssig, wenn uns ein 
Compiler-Spezialist verraten kann, wie man das verhindert.
Eventuell reicht es, wenn man alle in der Tabelle genannten Funktionen 
'inline' deklariert. Eventuell kann man auch den typedef mit inline 
versehen, das habe ich noch nie probiert. Meine CPUs haben immer ein 
klein wenig mehr Rechenleistung als ich brauche.

Wenn Du es noch viel schneller haben willst, wirst Du auf Assembler 
zurückgreifen müssen, oder Dir ein Linker-Script bauen müssen, dass für 
alle aufzurufenden Funktionen eine geradzahlige Startadresse vergibt.

Nehmen wir an, dass Du die Funktionen ab 0x4000 im Speicher hast und 
alle Funktionen jeweils max. 0x100 Bytes lang sind. Dann würde man sie 
also im Speicher unter 0x4000, 0x4100, 0x4200... finden.
Also 'Wert' um 8bit links shiften und 0x4000 auf-odern. Dann call auf 
diese Adresse.
das wiederum kann man noch einmal optimieren, in Assembler, denn ein 
call speichert die Rücksprungadresse auf dem Stack. Stattdessen kann ich 
einen JMP auf die Funktion machen und per JMP wieder zurück hinter meine 
Tabellen-Funktion.

Gruß, Ulrich

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.