Forum: Mikrocontroller und Digitale Elektronik switch-case-Anweisung zu langsam


von Blitzenblitz (Gast)


Lesenswert?

Ich schreibe gerade an einem C-Programm für einen AVR.
Dort habe ich eine unsigned char Variable Befehl.
Das werte ich mit switch case aus.
Hier nur ein Beispiel!
1
  switch(Befehl)
2
  {
3
    case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7
4
    case 1: if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
5
    case 2: if(PORTB &  (1 << 6)) PINB = (1 << 6); break;
6
    case 3: if(PORTB & ~(1 << 6)) PINB = (1 << 6); break;
7
    case 4: if(PORTB &  (1 << 5)) PINB = (1 << 5); break;
8
    case 5: if(PORTB & ~(1 << 5)) PINB = (1 << 5); break;
9
    case 6: if(PORTB &  (1 << 4)) PINB = (1 << 4); break;
10
    case 7: if(PORTB & ~(1 << 4)) PINB = (1 << 4); break;
11
    // Hier kommen noch 93 andere Befehle !!!
12
  }
Bei der switch-case-Anweisung stört mich, das das Programm bei sehr 
langen Listen jeden einzelnen case testen muß.
Gibt es eventuel noch eine andere Möglichkeit?

von ozo (Gast)


Lesenswert?

Eventuell mit "Befehl" einen Binärbaum durchlaufen?

von ozo (Gast)


Lesenswert?

Oder die Bitmaske für PORT und PIN in ner Tabelle nachschlagen? Mit 
Befehl als Index?

von dr.no (Gast)


Lesenswert?

Hast du bereits geschaut ob dein Compiler das switch() nicht über eine 
Sprungtabelle auflöst ?

Ansonsten mit Funktionspointern arbeiten.
1
const BefehlPtr[] =
2
{
3
   Befehl0,
4
   Befehl1,
5
   Befehl2,
6
   Befehl3
7
};

Ausführung dann mittels:
1
BefehlPtr[Befehl]();

von Peter (Gast)


Lesenswert?

Blitzenblitz schrieb:
> // Hier kommen noch 93 andere Befehle !!!

kannst du noch ein paar mehr zeigen? die Frage ist wie es weiter geht ob 
man das nicht komplett berechnen kann. Es wird zwar dadurch vermutlich 
nicht schneller aber es spart viel code.

Der Compiler sollte sotwas schön als Sprungtabelle ablegen, eventuell 
sollte man mal suchen warum er sotwas nicht macht.

von dr.no (Gast)


Lesenswert?

Es würde im übrigen helfen, wenn du schreibst was du vor hast.
Das Programmstück sieht so aus, als wolltest du die Möglichkeit 
implementieren über Befehle alle verfügbaren Pins zu steuern. Dafür gibt 
es in der Tat bessere Ansätze.

von Blitzenblitz (Gast)


Lesenswert?

dr.no schrieb:
> Ansonsten mit Funktionspointern arbeiten.

Sowas hatte ich im Sinn.
Leider bin ich mit Pointern in C noch nicht so fit...
Wo würde ich die ganzen Befehls-sequenzen hinschreiben?
Kannst du mir da mal ein geben?

von dr.no (Gast)


Lesenswert?

Blitzenblitz schrieb:
> Wo würde ich die ganzen Befehls-sequenzen hinschreiben?

In diesem Fall in eine separate Funktion.
1
typedef void (* FUNC_PTR)(void);
2
3
static void Befehl_0(void);
4
static void Befehl_1(void);
5
6
const FUNC_PTR befehle[] =
7
{
8
  Befehl_0,
9
  Befehl_1
10
  // ... weitere Befehle
11
};
12
13
14
static void Befehl_0(void)
15
{
16
  // ... code Befehl 0
17
}
18
19
static void Befehl_1(void)
20
{
21
  // ... code Befehl 1
22
}

Zugriff dann mit:
1
befehle[Befehl]();

Es empfiehlt sich zu prüfen ob "Befehl" innerhalb des gültigen Bereichs 
liegt bevor man das tut.

Nochmal: Es würde helfen wenn du erklärst was du vor hast.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Blitzenblitz schrieb:

> Bei der switch-case-Anweisung stört mich, das das Programm bei sehr
> langen Listen jeden einzelnen case testen muß.

Nein. Muss es eben nicht. Praktisch jeder C-Compiler weiß, wie's besser 
geht. Anhängig von der Anzahl/Dichte der Fälle sind ALternativen zB
 - binärer Entscheidungsbaum
 - Sprungtabelle

> Gibt es eventuel noch eine andere Möglichkeit?

Wie sieht der Code denn überhaupt aus? Wieso werden keine Sprungtabellen 
erzeugt?

von Karl H. (kbuchegg)


Lesenswert?

Funktionspointer sind hier auch nicht der Weisheit letzter Schluss. Das 
artet in Unmengen von Funktionen aus.

Am Besten:
Zurücklehenen, erst mal erzählen was die AUfgabenstellung aus einer 
höheren Werte gesehen ist und dann kann man entscheiden welche Technik 
angebracht ist.

So wie es jetzt aussieht, hast du einen verkorksten Ansatz. Den rettet 
aber auch eine Änderung auf tiefster Ebene nicht mehr.

So sind zum Beispiel in deinem Beispiel alle ungeraden Befehle Tests ob 
am PortB ein Bit gelöscht ist, und alle geraden Befehle sind Tests ob am 
Port (B?) ein bestimmtes Bit gesetzt ist. Alleine mit dieser 
Unterscheideung kann man schon mal 2 Gruppen bilden, bei der dann in 
weiterer Folge nur noch die Hälfte der Befehle geprüft werden muss.

von Peter (Gast)


Lesenswert?

wenn es wirklich so sein muss (was ich auch noch bezweifle) dann kann 
man immer noch zu ASM ausweichen. Da die Befehle alle gleich lang sind. 
Kann man einfach den

Adresszeiger = Adresszeiger + Befehltslänge * Befehl

rechnen und schon steht man an der richtigen stelle.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Peter schrieb:
> wenn es wirklich so sein muss (was ich auch noch bezweifle) dann kann
> man immer noch zu ASM ausweichen.

ARGL, nö. Hier geht's weiter:

Karl heinz Buchegger schrieb:

> So wie es jetzt aussieht, hast du einen verkorksten Ansatz. Den rettet
> aber auch eine Änderung auf tiefster Ebene nicht mehr.

von Kakadu (Gast)


Lesenswert?

In der Überschrift steht: "switch-case-Anweisung zu langsam"

Wie kommst Du überhaupt darauf? Hast Du irgendetwas gemessen?
Ich schätze mal: nein.

Und falls es wider Erwarten doch zu langsam abgearbeitet werden sollte, 
einfach den internen Takt von 1MHz auf 8MHz stellen, wenn wir vom AVR 
reden.

von Peter D. (peda)


Lesenswert?

Man sollte versuchen, das Gemeinsame aller Cases zu erkennen und dann 
die Funktion mit ner Variable zu schreiben, die den Unterschied 
abbildet.
1
#include <io.h>
2
3
uint8_t BITMASK[] = { 0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1 };
4
5
void setclr( uint8_t befehl )
6
{
7
  uint8_t dir = 0;
8
9
  if( befehl < 16 ){
10
    if( befehl & 1 )
11
      dir = 0xFF;
12
    PINB = (PORTB ^ dir) & BITMASK[befehl / 2];
13
  }
14
}


Peter

von MaWin (Gast)


Lesenswert?

> Bei der switch-case-Anweisung stört mich, das das Programm bei
> sehr langen Listen jeden einzelnen case testen muß.

Kaum.
Also mich würde eher stören, daß der ganze Code nichts taugt.

    case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7

wird mitnichten ein Bit löschen

    case 1: if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7

was willst du an PIN auch setzen ?


Warum schreibst du nicht funktionierende Programme ?

if(befehl&1) // Setzen
{
    PORTB|=1<<(befehl>>1);
}
else // löschen
{
    PORTB&=~(1<<(befehl>>1));
}

erledigt deine 16 ersten Befehle, und es funktioniert damit sogar.

Ersetzt man PORTB noch alle 16 befehle durch das Richtige

int PORT[5]={PORTB,PORTA,PORTC,PORTD,PORTE};

if(befehl&1) // Setzen
{
    *PORT[befehl>>4]|=1<<(befehl>>1);
}
else // löschen
{
    *PORT[befehl>>4]&=~(1<<(befehl>>1));
}

erschlägt der Code gar alle deine 100 Befehle (funktioniert aber so auf 
einem AVR wohl nicht weil das nicht deinen Befehlen entspricht).

von Karl H. (kbuchegg)


Lesenswert?

MaWin schrieb:

> Ersetzt man PORTB noch alle 16 befehle durch das Richtige
>
> int PORT[5]={PORTB,PORTA,PORTC,PORTD,PORTE};
>
> if(befehl&1) // Setzen
> {
>     *PORT[befehl>>4]|=1<<(befehl>>1);
> }
> else // löschen
> {
>     *PORT[befehl>>4]&=~(1<<(befehl>>1));
> }

Ja, so ungefähr hätte ich mir das auch vorgestellt. Daher die 
Aufforderung an den TO, mal etwas mehr zu erzählen. Dann könnte man 
entscheiden ob die Stossrichtung stimmt.

Auch Hybridmethoden wären denkbar (er wird ja doch nicht für jedes Bit 
an jedem Port einen eigenen Befehl eingeführt haben. Oder doch?), wenn 
es ausser dem gezeigten 'Befehlsschema' noch andere gibt.

> erschlägt der Code gar alle deine 100 Befehle (funktioniert aber so auf
> einem AVR wohl nicht weil das nicht deinen Befehlen entspricht).

Liese sich aber leicht erreichen. Das wäre nicht das Problem.

von Blitzenblitz (Gast)


Lesenswert?

Habe dieses von MaWin mal ausprobiert:
1
int PORT[3]={PORTB,PORTC,PORTD};
2
3
if(Befehl&1) // Setzen
4
{
5
    *PORT[Befehl>>4] |=  (1<<(Befehl>>1));
6
}
7
else // löschen
8
{
9
    *PORT[Befehl>>4] &= ~(1<<(Befehl>>1));
10
}
bekomme leider die Fehlermeldungen:
invalid type argument of 'unary *' (have 'int')

von Karl H. (kbuchegg)


Lesenswert?

1
#include <avr/io.h>
2
3
volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };
4
5
int main()
6
{
7
  uint8_t Befehl = 23;
8
9
  if(Befehl&1) // Setzen
10
  {
11
    *PORT[Befehl>>4] |=  (1<<((Befehl&0x0F)>>1));
12
  }
13
  else // löschen
14
  {
15
    *PORT[Befehl>>4] &= ~(1<<((Befehl&0x0F)>>1));
16
  }
17
18
  // oder, da Befehl ja sowieso ein uint8_t (also unsigned) ist
19
  // da wirds dann vielleicht ein wenig klarer was da passiert
20
  if( Befehl % 2 == 1 )
21
  {  // die Befehle mit ungerader Befehlsnummer
22
    *PORT[Befehl/16] |=  (1 << (Befehl%16/2));
23
  }
24
  else // löschen
25
  {
26
    *PORT[Befehl/16] &= ~(1 << (Befehl%16/2) );
27
  }
28
29
  // den Teil mit der variablen Bitschieberei könnte man noch ersetzen
30
31
  {
32
    uint8_t SetMask[]   = { 0x01,  0x02,  0x04,  0x08,  0x10,  0x20,  0x40, 0x80 };
33
    uint8_t ClearMask[] = {~0x01, ~0x02, ~0x04, ~0x08, ~0x10, ~0x20, ~0x40, 0x7F };
34
35
    if( Befehl % 2 == 1 )
36
    {  // die Befehle mit ungerader Befehlsnummer
37
      Befehl = Befehl / 2;
38
      *PORT[Befehl/8] |=  SetMask[ Befehl % 8 ];
39
    }
40
    else // löschen
41
    {
42
      Befehl = Befehl / 2;
43
      *PORT[Befehl/8] &= ClearMask[ Befehl % 8 ];
44
    }
45
46
  }
47
}

von Blitzenblitz (Gast)


Lesenswert?

mit
1
volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };
bekomme ich die Meldung: Build succeeded with 0 Warnings.

Ohne das volatile bekomme ich allerdings die Warnung:
initialisation discards qualfiers from pointer target type

von Karl H. (kbuchegg)


Lesenswert?

Blitzenblitz schrieb:
> mit
>
1
> volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };
2
>
> bekomme ich die Meldung: Build succeeded with 0 Warnings.

Mit anderen Worten: alles in Ordnung.

Auch wenn ich manchmal Fehler mache, ich bin ja auch nicht auf der 
Nudelsuppe dahergeschwommen gekommen.

> Ohne das volatile bekomme ich allerdings die Warnung:
> *initialisation discards qualfiers from pointer target type*

Mit anderen Worten: das volatile muss da sein. Und genau deswegen habe 
ich es hingeschrieben. qed

von Blitzenblitz (Gast)


Lesenswert?

Ja, besten Dank.

1
if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7
2
3
if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
Ich habe das mit PINB bei mir im Programm, damit es interruptbar ist.
Wenn ich es so mache:
1
PORTB |=  (1 << 7); // SET PB7
2
3
PORTB &= ~(1 << 7); // CLR PB7
Ist es eine Read Modify Write Sequenz
Wird diese von einem Interrupt unterbrochen, der ebenfalls PORTB 
verfummelt dann kann das in die Hose gehn...

von Karl H. (kbuchegg)


Lesenswert?

Blitzenblitz schrieb:

> Wird diese von einem Interrupt unterbrochen, der ebenfalls PORTB
> verfummelt dann kann das in die Hose gehn...

du kannst ja immer noch ein sei/cli drum herum machen :-)
Von Interrupts war ja bisher nicht die Rede.

von MaWin (Gast)


Lesenswert?

> bekomme leider die Fehlermeldungen

Drum schrieb ich "funktioniert aber so auf einem AVR wohl
nicht" (weil mir die Definitonsweise des PORTB dort
unbekannt ist).

Also nicht stumpf abtippen, sondern selber denken,
hat Karl heinz ja auch geschafft und sogar das
fehlende &0x0F nachgerüstet, obwohl ich &7 genommen hätte:

  if(Befehl&1) // Setzen
  {
    *PORT[Befehl>>4] |=  (1<<((Befehl>>1)&7));
  }
  else // löschen
  {
    *PORT[Befehl>>4] &= ~(1<<((Befehl>>1)&7));
  }

von Blitzenblitz (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> du kannst ja immer noch ein sei/cli drum herum machen :-)

Wie geht das?

von Blitzenblitz (Gast)


Lesenswert?

Karl heinz Buchegger schrieb:
> du kannst ja immer noch ein sei/cli drum herum machen :-)

Ah ja sei ist ein Assemblerbefehl und heißt SEt Interrupt
und cli heißt CLear Interrupt.
Ich schreibe in C:
1
sei(); // Interrupts freigeben
2
PORTB |=  (1 << 7); // SET PB7
3
cli(); // Interrupts sperren
Wobei zu testen ist, ob das länger dauer als mein
1
if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
und ob ich mir damit nicht noch andere Probleme einhandle...

von (prx) A. K. (prx)


Lesenswert?

MaWin schrieb:

>     case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7
> wird mitnichten ein Bit löschen

Doch. Nicht auf Mega8/32, wohl aber auf Mega88/324.
1 auf PINx schreiben hat da Toggle-Funktion.

Besser wär aber:
     case 0: PINB = PORTB & (1 << 7);

von Εrnst B. (ernst)


Lesenswert?

Blitzenblitz schrieb:

> Ah ja sei ist ein Assemblerbefehl und heißt SEt Interrupt
> und cli heißt CLear Interrupt.
> Ich schreibe in C:sei(); // Interrupts freigeben
> PORTB |=  (1 << 7); // SET PB7
> cli(); // Interrupts sperren
> Wobei zu testen ist, ob das länger dauer als meinif(PORTB & ~(1 << 7)) PINB = (1 
<< 7); break; // SET PB7
> und ob ich mir damit nicht noch andere Probleme einhandle...

Du kannst auch einfach dem GCC das Optimieren erlauben. Aus
  PORTB |= (1<<7);
wird dann:
 256:   c7 9a           sbi     0x18, 7 ; 24

Also ein einzelner "sbi"-Befehl. Der ist Atomar, kein sei/cli nötig.

Bei der Tabellen-Version oben, da brauchst du das ggfs.

von (prx) A. K. (prx)


Lesenswert?

Εrnst B✶ schrieb:

> Also ein einzelner "sbi"-Befehl. Der ist Atomar, kein sei/cli nötig.

Aber Vorsicht! Nicht alle Ports aller AVRs liegen in von SBI/CBI 
erfassbaren Adressraum.

von Blitzenblitz (Gast)


Lesenswert?

Danke an alle fleißigen Helfer,
habe dieses jetzt mal programmiert und simuliert:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
//***********************************************
4
typedef void (* FUNC_PTR)(void);
5
void Befehl_0(void);
6
void Befehl_1(void);
7
void Befehl_2(void);
8
void Befehl_3(void);
9
void Befehl_4(void);
10
void Befehl_5(void);
11
void Befehl_6(void);
12
void Befehl_7(void);
13
void Befehl_8(void);
14
void Befehl_9(void);
15
//***********************************************
16
const FUNC_PTR befehle[] =
17
{
18
  Befehl_0,
19
  Befehl_1,
20
  Befehl_2,
21
  Befehl_3,
22
  Befehl_4,
23
  Befehl_5,
24
  Befehl_6,
25
  Befehl_7,
26
  Befehl_8,
27
  Befehl_9
28
};
29
//***********************************************
30
int main(void)
31
{
32
  unsigned char Befehl = 0;
33
  DDRB = 0xFF;
34
  while(1)
35
  {
36
    befehle[Befehl++]();
37
    if(Befehl == 10) Befehl = 0;
38
  }
39
}
40
//***********************************************
41
void Befehl_0(void) {if(~PORTB & (1 << 4)) PINB = (1 << 4);}
42
void Befehl_1(void) {if(~PORTB & (1 << 3)) PINB = (1 << 3);}
43
void Befehl_2(void) {if(~PORTB & (1 << 2)) PINB = (1 << 2);}
44
void Befehl_3(void) {if(~PORTB & (1 << 1)) PINB = (1 << 1);}
45
void Befehl_4(void) {if(~PORTB & (1 << 0)) PINB = (1 << 0);}
46
void Befehl_5(void) {if( PORTB & (1 << 4)) PINB = (1 << 4);}
47
void Befehl_6(void) {if( PORTB & (1 << 3)) PINB = (1 << 3);}
48
void Befehl_7(void) {if( PORTB & (1 << 2)) PINB = (1 << 2);}
49
void Befehl_8(void) {if( PORTB & (1 << 1)) PINB = (1 << 1);}
50
void Befehl_9(void) {if( PORTB & (1 << 0)) PINB = (1 << 0);}
Scheint zu funktionieren.
Der tiefe Sinn dahinter, den ich bisher verschwiegen habe, war:
1. Meine C-Kenntnisse zu verbessern...
2. Eine Möglichkeit zu finden, historische oder selbsterdachte µC's auf 
einem AVR in Software nachbilden zu können...

von Erich (Gast)


Lesenswert?

>Der tiefe Sinn dahinter
>"switch-case-Anweisung zu langsam"

Die "Lösung" vom Datum: 12.01.2011 16:21 wird  NIEMALS  schneller sein 
als der Anfang dises Threads.
Sie ist nur
= umständlicher
= schlechter lesbar
= schlechter wart- und erweiterbar  (aufwendiger)

von Blitzenblitz (Gast)


Lesenswert?

Erich schrieb:
> Die "Lösung" vom Datum: 12.01.2011 16:21 wird  NIEMALS  schneller sein
> als der Anfang dises Threads.
> Sie ist nur
> = umständlicher
> = schlechter lesbar
> = schlechter wart- und erweiterbar  (aufwendiger)

Nö, eigentlich bin ich ganz zufrieden.
Ich würde im weiteren Verlauf den Befehlen dann natürlich sinnvollere 
Namen als Befehl_123 geben.
Mein eigentliches Programm würde dann hier stehen:
1
const FUNC_PTR befehle[] =
2
{
3
  SET_PB1,
4
  SET_PB3,
5
  SET_PB5,
6
  SWAP_A_at_R0, // Befehl wie beim 8051, nur Gedankenspiel
7
  CLR_PB2,
8
  CLR_PB4,
9
// usw.
10
};
Kritiker werden einwenden können ich hätte das ja auch so machen können:
1
#define SET_PB4 {if(~PORTB & (1 << 4)) PINB = (1 << 4);}
2
#define CLR_PB4 {if( PORTB & (1 << 4)) PINB = (1 << 4);}
3
// usw.
Allerdings würde der Preprozzessor dann für jeden Befehl den kompletten 
Code einfügen und ruckzuck wär mein Speicher voll.

von Falk B. (falk)


Lesenswert?

@  Blitzenblitz (Gast)

>Mein eigentliches Programm würde dann hier stehen:

>const FUNC_PTR befehle[] =

Und auf dem Besten weg, ein langsames, akademisches Monster zu werden. 
Diese ganzen Structs und Datenzugriffe kosten Zeit, auch auf einem 
schnellen Prozessor. Zeit, die du angeblich nicht hast.

>Allerdings würde der Preprozzessor dann für jeden Befehl den kompletten
>Code einfügen

Das ist der Sinn der Sache, wenn es schnell sein soll. Lies mal den 
Betreff DEINES Beitrags . . .

> und ruckzuck wär mein Speicher voll.

Nimm einen größeren Prozessor. Und vor allem musst du schon VERDAMMT 
viele solche Sachen machen, um mit ein paar IO Klimpereien einen 
normalen uC vollzustopfen.

Ergo: Thema verfehlt, sechs.

MFG
Falk

von Blitzenblitz (Gast)


Angehängte Dateien:

Lesenswert?

@Falk Brunner: :-(

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.