Forum: Mikrocontroller und Digitale Elektronik Switch Case verzweigung so Anwendbar auf ATmega 32?


von Jens F. (neiwirger)


Lesenswert?

Guten abend zusammen:
Ich habe eine Spezielle frage zur Switch Case Verzweigung.
Ich arbeite mit einem Atmega 32 bei 1Mhz internem Takt. Das programm 
soll später noch ausgebaut werden.
Und zwar lasse ich den 8bit Timer bei einem Impuls loslaufen, mit 
prescaler 256, und wenn dieser Impuls abbricht stoppt der 8bit Timer.
Anschließend gebe ich den Wert an den uint8_t mit dem Namen "zaehler".

Dann möchte ich eine switch case Verzweigung einbauen die Wiefolgt 
aussehen soll:
1
switch (zaehler)
2
      {
3
      case (>=8) && (<=10):
4
           PORTA |= (1<<PA0);
5
           break;
6
7
      case (>=11) && (<=13):
8
           PORTA |= (1<<PA0);
9
           break;
10
      }  //usw. usw.
Muss ich die Überprüfungen nach case nochmals in eine gesamtklammer 
setzen also "case ((>=8) && (<=10));"
oder bin ich mit diesem gedanken total falsch und ich muss alles in else 
if schreiben?


Ich wäre um euren Rat sehr dankbar,

von perlbastel (Gast)


Lesenswert?

Jens Freiburger schrieb:
> case ((>=8) && (<=10));
So wird das nichts. Der GCC hat eine Erweiterung dafür 
http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Case-Ranges.html , aber die 
Syntax ist anders (s. Link).

Ansonsten if-else oder
1
switch(blub)
2
{
3
  case 5:
4
  case 6:
5
  case 7:
6
    tu_was;
7
    break;
8
  case 8:
9
  case 9:
10
  ...
11
}

von Jannik O. (jannipanni)


Lesenswert?

Soweit ich das lese, müste da alles so funktionieren. Aber es noch 
einmal alles in Klammern zu setzen wäre noch ein Schritt sicherer und 
falsch machst du damit sicher nichts.

von Jens F. (neiwirger)


Lesenswert?

Also am besten mal drauf schmeissen und probieren.
Danach ist man ja immer schlauer
Und falls es nicht funktioniert werde ich eben ne if-else verzweigung 
anwenden.
Danke euch

von perlbastel (Gast)


Lesenswert?

Also ich kriege die Version mit den Vergleichen gar nicht erst durch den 
Compiler (allerdings für x86), von daher...
1
main.c|26|error: syntax error before '>=' token|

Zähler schreibt man übrigens mit 'h'.

von Jens F. (neiwirger)


Lesenswert?

@ perlbastel:
Vielen dank, so wie in dem Link angegeben funktioniert das.
Zumindest mein Compiler übersetzt mir das nun ohne Probleme.

von perlbastel (Gast)


Lesenswert?

Jens Freiburger schrieb:
> Zumindest mein Compiler übersetzt mir das nun ohne Probleme.
Ja, so wie im Link angegeben funktioniert es natürlich. Was nicht 
funktioniert (bzw. sich nicht übersetzen lässt) ist dein erster Versuch 
(>=8) && (<=10).

von Jens F. (neiwirger)


Lesenswert?

Ja, das habe ich nun am eigenen Leibe erfahren.
Das mit dem h im Zähler habe ich bereits Korrigiert. Danke (nicht zur 
Strafe nur zur Übung heisst es ja)
Morgen bau ich die Schaltung dazu auf.
Einen schönen abend euch

von Peter D. (peda)


Lesenswert?

Bereiche schreibt man so:
1
switch (zaehler)
2
      {
3
      case 8 ... 10:
4
           PORTA |= (1<<PA0);
5
           break;
6
7
      case 11 ... 13:
8
           PORTA |= (1<<PA0);
9
           break;
10
      }  //usw. usw.


Peter

von (prx) A. K. (prx)


Lesenswert?

Aber eben nur in GNU-C, nicht in Standard-C.

von Peter D. (peda)


Lesenswert?

A. K. schrieb:
> Aber eben nur in GNU-C, nicht in Standard-C.

Leider.
In Standard-C muß man die 65536 Case alle untereinander schreiben.
Und bei 32 oder 64 Bit: no way.


Peter

von Oliver (Gast)


Lesenswert?

Nicht umsonst gibts dafür ja noch if...else.

Oliver

von neiwirger (Gast)


Lesenswert?

Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard 
C?
Bzw.
So lange es funktioniert und ich der alleinige Programmierer bin an dem 
Projekt sollte es keine Probleme geben,
oder sehe ich das zu locker?

von (prx) A. K. (prx)


Lesenswert?

neiwirger schrieb:
> oder sehe ich das zu locker?

Nein, das ist ok. Ich hatte nur extra darauf hingewiesen, weil das im 
Kontext vom Thread etwas unterging.

von Rolf Magnus (Gast)


Lesenswert?

neiwirger schrieb:
> Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard
> C?

Nein, aber wenn du nicht GNU-C nimmst, reißt dir der Compiler den Kopf 
runter.
Der Ursprungsposter hat nirgends erwähnt, welchen Compiler er nutzt.

> Bzw.
> So lange es funktioniert und ich der alleinige Programmierer bin an dem
> Projekt

... und du nicht vorhast, es jemals wo anders hin zu portieren...

> sollte es keine Probleme geben, oder sehe ich das zu locker?

Ich überlege mir normalerweise erst, ob der Vorteil wirklich signifikant 
ist. Wenn der Code durch die Verwendung einer GCC-Erweiterung nicht 
deutlich besser lesbar, kürzer oder übersichtlicher wird, nutze ich sie 
nicht.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

neiwirger schrieb:
> Reisst ihr mir nun den Kopf runter wenn ich GNU-C nehme anstatt Standard
> C?

Nö. Das tut nur der Standard-C-Compiler, wenn Du ihn irgendwann 
verwenden musst.

Compilerspezifische Spracherweiterungen mögen in gewissen Situationen 
sinnvoll sein, es ist aber ratsam, sie nicht zu verwenden, wenn man 
seine Programmierkenntnisse nicht auf ein Inseldasein beschränken 
möchte.

Das eingangs genannte switch/case-Konstrukt lässt sich problemlos als 
einfache if-Verzweigung ausdrücken.

Und so eine if-Verzweigung dürfte auch jeder C-Compiler nicht minder 
performant übersetzen als ein dafür gebasteltes switch/case-Konstrukt.

von Peter D. (peda)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Das eingangs genannte switch/case-Konstrukt lässt sich problemlos als
> einfache if-Verzweigung ausdrücken.

Aber es gibt Beispiele, wo die Lesbarkeit deutlich leidet:
1
  switch (x){
2
    case 0 ... 9:
3
    case 11 ... 17:
4
    case 34 ... 39:
5
      // mache was
6
      break;
7
    case 30 ... 33:
8
    case 40 ... 44:
9
    case 50 ... 55:
10
      // mache was anderes
11
      break;
12
  }
Das ist mit if/else nur schwer zu schreiben.

Rufus Τ. Firefly schrieb:
> Und so eine if-Verzweigung dürfte auch jeder C-Compiler nicht minder
> performant übersetzen als ein dafür gebasteltes switch/case-Konstrukt.

Beim AVR-GCC nicht.
Beim Switch macht er eine common subexpression elimination, wenn mehrere 
Case mit gleichen Passagen enden.
Beim If dagegen nicht.


Peter

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Peter Dannegger schrieb:
> Aber es gibt Beispiele, wo die Lesbarkeit deutlich leidet:

Da sehe ich dann eher eine algorithmische Herausforderung. Wenn ein 
solcher Wertebereich auftritt, würde ich mich fragen, warum der so ... 
seltsam aussieht und ob sich das nicht an der Quelle vereinfachen lässt.

von neiwirger (Gast)


Lesenswert?

Ok.
Ich lasse das in diesem Fall so stehen.
Allerdings notiere ich mir dahinter dass das in GNU geht, in Standard C 
nicht.
Sehr interessant das ganze, wenn auch komplex

von Peter D. (peda)


Lesenswert?

Rufus Τ. Firefly schrieb:
> Wenn ein
> solcher Wertebereich auftritt, würde ich mich fragen, warum der so ...
> seltsam aussieht

Ich nicht.
Die Anforderungen kommen ja von außen (z.B. ne Statemaschine, ADC-Werte, 
Codierung, Verschlüsselung) und ich werde mir nicht die Blöße geben, 
dann zu sagen, daß es nicht geht.
Das Programm muß die Aufgabe lösen und nicht umgekehrt.

Auch hat der Switch den Vorteil, daß man überlappende Case um die Ohren 
gehauen kriegt.
Überlappende Ifs dagegen nicht und dann sucht man wie blöde an der 
falschen Stelle, warum ein hinteres If nicht ausgeführt wird.


Peter

von Yalu X. (yalu) (Moderator)


Lesenswert?

Wenn die einzelnen abzufragenden Intervalle lückenlos zusammenhängend
sind wie im ursprünglichen Beispiel, würde ich immer die If-Methode
bevorzugen (oder, wenn es sehr viele Intervalle sind, eine Kombination
aus Tabelle, Schleife und If-Abfrage).

Also statt diesem
1
  switch (z) {
2
    case  8 ... 10:
3
      aktion1;
4
      break;
5
    case 11 ... 13:
6
      aktion2;
7
      break;
8
    case 14 ... 17:
9
      aktion3;
10
      break;
11
    case 18 ... 20:
12
      aktion4;
13
      break;
14
  }

dieses:
1
  if      (z <  8)
2
    ;
3
  else if (z < 11)
4
    aktion1;
5
  else if (z < 14)
6
    aktion2;
7
  else if (z < 18)
8
    aktion3;
9
  else if (z < 21)
10
    aktion4;
11
  else // (z >=21)
12
    ;

Die Gründe dafür:

1. Weniger Redundanz im Quelltext: Bei der Case-Methode tauchen die
   oberen Intervallgrenzen einer Case-Abfrage um 1 erhöht in der
   nächsten Case-Abfrage noch einmal auf. Will man eine der Grenzen
   verschieben, muss man das immer an zwei Stellen tun.

2. Bei der If-Methode können leicht Aktionen eingefügt werden für den
   Fall, dass der abzufragende Wert kleiner als die unterste Grenze oder
   größer als die oberste Grenze ist (s. die leeren Anweisungen (;) im
   obigen Beispiel). Bei der Case-Methode bräuchte man eine Notation der
   Art

1
   case    ... 7:  // alle Werte <= 7
2
3
   case 21 ...  :  // alle Werte >= 21

   die es aber auch beim GCC nicht gibt. Man kann das zwar auch anders
   hintricksen, aber sehr schön sieht das am Ende nicht aus.

3. Das Thema Portabilität wurde ja schon angesprochen.

4. Bei der If-Methode tauchen nur die relevanten Vergleiche im Quellcode
   auf. Bei der Case-Methode beinhaltet jeder Case zwei Vergleiche. Der
   GCC optimiert das zwar, aber bei der If-Methode braucht man sich
   nicht auf die Optimierungsfähigkeiten des Compilers zu verlassen.

5. Trotz Optimierung erzeugt der GCC bei der Case-Methode meist längeren
   Code. Wenn man Pech hat, legt er eine Sprungtabelle mit jeweils einem
   Sprung für jeden in den Intervallen enthaltenen Zahlenwert an, was
   dann trotz weniger Cases viel Programmspeicher beanspruchen kann.

Bei sehr vielen abzufragenden Intervallgrenzen ist eine Sprungtabelle
schneller als die sequentiellen If-Abfragen, bei denen der Rechenaufwand
linear mit der Anzahl der Intervallgrenzen steigt. Wenn die Rechenzeit
aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen
so anordnen, dass der Aufwand nur logarithmisch steigt.

Es gibt natürlich andere Fälle wie den von Peter gezeigten, wo die
einzelnen Intervalle nicht zusammenhängend sind und deswegen die
Case-Methode vorteilhaft ist. Mir ist so ein Fall aber noch nie
untergekommen.

Peter Dannegger schrieb:
> Die Anforderungen kommen ja von außen (z.B.

> ne Statemaschine,

Für Zustände (typischerweise symbolisch als Enum definiert) würde ich
nie Bereichsabfragen verwenden, da damit die das Abfrageergebnis von der
Reihenfolge der Symbole im Enum abhängt. Zustände sind — von Ausnahmen
abgesehen — von Natur aus nun einmal  nicht totalgeordnet.

> ADC-Werte,

Das kann vielleicht einmal vorkommen, aber typischerweise haben hier die
abzufragenden Bereiche keine Lücken.

> Codierung, Verschlüsselung)

Mag sein, allerdings fällt mir auch hierzu kein sinnvolles Beispiel ein.

von (prx) A. K. (prx)


Lesenswert?

Yalu X. schrieb:

> 5. Trotz Optimierung erzeugt der GCC bei der Case-Methode meist längeren
>    Code. Wenn man Pech hat, legt er eine Sprungtabelle mit jeweils einem
>    Sprung für jeden in den Intervallen enthaltenen Zahlenwert an, was
>    dann trotz weniger Cases viel Programmspeicher beanspruchen kann.

Der Codegenerator schätzt die Grösse ab und erzeugt den Code 
entsprechend.

> Bei sehr vielen abzufragenden Intervallgrenzen ist eine Sprungtabelle
> schneller als die sequentiellen If-Abfragen, bei denen der Rechenaufwand
> linear mit der Anzahl der Intervallgrenzen steigt.

In nicht-trivialen Fällen wird der Compiler keine sequentiellen Abfragen 
produzieren, sondern einen binären Abfragebaum. Damit steigt der Aufwand 
nicht linear, sondern logarithmisch.

Wie effizient das hinsichtlich der Laufzeit ist hängt stark davon ab, 
wie schnell Sprünge sind und wie gross der Overhead einer Sprungtabelle 
ist. Bei AVRs ist der Overhead einer Sprungtabelle sehr gross, bedingte 
Sprünge sind jedoch schnell. Bei PC-Prozessoren ist es eher umgekehrt 
und die hohe Sprungdichte goutiert auch nicht jeder Prozessor.

> Wenn die Rechenzeit
> aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen
> so anordnen, dass der Aufwand nur logarithmisch steigt.

Ebendies macht bereits der Compiler beim switch - aber wohl nur dort.

von Yalu X. (yalu) (Moderator)


Lesenswert?

A. K. schrieb:
>> Wenn die Rechenzeit
>> aber wirklich ein Problem ist, kann man bei der If-Methode die Abfragen
>> so anordnen, dass der Aufwand nur logarithmisch steigt.
>
> Ebendies macht bereits der Compiler beim switch - aber wohl nur dort.

Danke, das habe ich nicht gewusst, muss ich mal bei Gelegenheit testen.

> Der Codegenerator schätzt die Grösse ab und erzeugt den Code
> entsprechend.

Wobei die Betonung auf "schätzt" liegen sollte ;-)

Ein Beispiel:
1
#include <stdint.h>
2
3
volatile uint8_t aktion1, aktion2, aktion3, aktion4;
4
5
void sub1(uint8_t z) {
6
  switch (z) {
7
    case 12 ... 13:
8
      aktion1;
9
      break;
10
    case 14 ... 15:
11
      aktion2;
12
      break;
13
    case 16 ... 17:
14
      aktion3;
15
      break;
16
    case 18 ... 36:
17
      aktion4;
18
      break;
19
  }
20
}

ergibt bei -Os
1
sub1:
2
  ldi r25,lo8(0)
3
  mov r30,r24
4
  mov r31,r25
5
  sbiw r30,12
6
  cpi r30,25
7
  cpc r31,__zero_reg__
8
  brsh .L1
9
  subi r30,lo8(-(gs(.L7)))
10
  sbci r31,hi8(-(gs(.L7)))
11
  ijmp
12
.L7:
13
  rjmp .L3
14
  rjmp .L3
15
  rjmp .L4
16
  rjmp .L4
17
  rjmp .L5
18
  rjmp .L5
19
  rjmp .L6
20
  rjmp .L6
21
  rjmp .L6
22
  rjmp .L6
23
  rjmp .L6
24
  rjmp .L6
25
  rjmp .L6
26
  rjmp .L6
27
  rjmp .L6
28
  rjmp .L6
29
  rjmp .L6
30
  rjmp .L6
31
  rjmp .L6
32
  rjmp .L6
33
  rjmp .L6
34
  rjmp .L6
35
  rjmp .L6
36
  rjmp .L6
37
  rjmp .L6
38
  .text
39
.L3:
40
  lds r24,aktion1
41
  ret
42
.L4:
43
  lds r24,aktion2
44
  ret
45
.L5:
46
  lds r24,aktion3
47
  ret
48
.L6:
49
  lds r24,aktion4
50
.L1:
51
  ret

Zum Vergleich:
1
#include <stdint.h>
2
3
volatile uint8_t aktion1, aktion2, aktion3, aktion4;
4
5
void sub2(uint8_t z) {
6
  if      (z < 12)
7
    ;
8
  else if (z < 14)
9
    aktion1;
10
  else if (z < 16)
11
    aktion2;
12
  else if (z < 18)
13
    aktion3;
14
  else if (z < 37)
15
    aktion4;
16
  else // (z >=37)
17
    ;
18
}

ergibt bei -Os
1
sub2:
2
  cpi r24,lo8(12)
3
  brlo .L8
4
  cpi r24,lo8(14)
5
  brsh .L10
6
  lds r24,aktion1
7
  ret
8
.L10:
9
  cpi r24,lo8(16)
10
  brsh .L11
11
  lds r24,aktion2
12
  ret
13
.L11:
14
  cpi r24,lo8(18)
15
  brsh .L12
16
  lds r24,aktion3
17
  ret
18
.L12:
19
  cpi r24,lo8(37)
20
  brsh .L8
21
  lds r24,aktion4
22
.L8:
23
  ret

Die Case-Variante ist mehr als doppelt so lang und dürfte im Mittel
trotz der Sprungtabelle sogar langsamer sein.

von (prx) A. K. (prx)


Lesenswert?

Nimm mal einen AVR oberhalb 8K Flash, dann kriegst du den 
Entscheidungsbaum. Die Aufwandsabschätzung für die kleinen AVRs ist 
überholungsbedürftig. Siehe auch 
Beitrag "Switch Case compiler übersetzung in asm. fehlerhaft"

von Jens F. (neiwirger)


Lesenswert?

Ich möchte mich nochmal melden da ich mir jetzt den Beitrag nochmals 
komplett durchgelesen habe.

Mir ist es wichtig das ich eine einigermaßen genaue Abfrage habe.
Deshalb war mein gedanke die Switch - case Struktur zu nehmen und 
dadurch, dank der Verlinkung von perlbastel ziemlich am Anfang des 
Themas, den Wert den ich vom Zähler nach meiner Rechnung auf dem Blatt 
Papier erhalten soll eingrenzen kann.
1
8 ... 11
Denn der Wert sollte eine Bestimmte zeit weder über, noch 
unterschreiten.
Da ich 7 verschiedene Gänge habe, habe ich diese komplett ausgerechnet 
(In Bezug auf Prescaler und Signal zeit).
Wenn ich nun aber If - else if nehme, dann habe ich doch immer nur die 
abfrage z.B. "kleiner als 8". Somit fällt ein Signal das gerade so die 
neun als Zählerstand hat in den nächstgrößeren bereich. Das Signal kann 
und darf durchaus etwas Variieren, deshalb die Abfrage zwischen zwei 
Werten.

So nebenbei merke ich gerade dass das doch auch mit if möglich sein 
sollte:
1
if((zaehler >= 8) && (zaehler <= 11))

Das sollte doch die gleiche wirkung haben wie:
1
case 8 ... 11

Ich danke euch für das Aufzeigen dieser möglichkeit.
Die Switch Case Variante wollte ich zuerst nehmen, da ich annahm if und 
else if in so einem fall zu nehmen wäre vorsichtig ausgedrückt unedel.

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.