Hi.
Ich lasse eine Variable mit einem Interrupt hochzählen.
Dann möchte ich, wenn die Variable zwischen 0-100 liegt eine Funktion
aufrufen, wenn sie zwischen 101-200 liegt eine andrer Funktion aufrufen
usw.
Ich könnte es mit if-Vergleichen programmieren.
Aber dann wird es schnell unübersichtlich.
Kann man das auch ohne if mit Switch-Case programmieren?
Wenn ja wie?
Vielen Dank im Vorraus
Ohmisch schrieb:> Hi.>> Ich lasse eine Variable mit einem Interrupt hochzählen.> Dann möchte ich, wenn die Variable zwischen 0-100 liegt eine Funktion> aufrufen, wenn sie zwischen 101-200 liegt eine andrer Funktion aufrufen> usw.> Ich könnte es mit if-Vergleichen programmieren.> Aber dann wird es schnell unübersichtlich.
Was ist daran
1
if(var>=0&&var<=100)
2
func1();
3
elseif(var>=101&&var<=200)
4
func2();
unübersichtlich?
> Kann man das auch ohne if mit Switch-Case programmieren?
Nein. Naja, man könnte case 1: case 2: case ...n: func1(); break;
schreiben, das wäre wirklich unübersichtlich.
Solange man beim gcc bleibt, ist das sicher besser - wie so vieles beim
gcc.
Zum Thema Rechenzeit: es kommt natürlich deauf, was alles in den Zweigen
passiert und ob die Entscheidung ium switch überhaupt ins Gewicht fällt.
Natürlich kostet eine Division Arbeit, aber umsonst sind die anderen
Lösungen auch nicht (Sprungtabellen bspw. kosten Platz).
Peter Dannegger schrieb:> Da freut sich aber die CPU, daß sie richtig schön Zyklen verbraten darf> für die Division.
Könnte ja auch auf nem PC sein, auf dem sich die CPU oft langweilt ;-)
Ohmisch schrieb:> Alles klar ich wollte nur wissen ob so was möglich wär.> Dann mach ich es mit if.
Aber mach die Bereichsabfragen ein bißchen intelligent.
eine unsigned Zahl kann zb schon mal grundsätzlich nicht kleiner als 0
sein. Wenn besagte Zahl dann nicht beispielsweise im Bereich 0 bis 100
liegt, dann brauchst du in der 2.ten Abfrage auch nicht mehr testen, ob
sie größer als 100 ist. Sie muss es sein, sonst wärst du nicht in diesem
Fall gelandet.
Also nicht
1
if(var>=0&&var<=100)
2
func1();
3
elseif(var>=101&&var<=200)
4
func2();
sondern
1
if(var<=100)
2
func1();
3
elseif(var<=200)
4
func2();
liefert genau die gleiche Logik, spart aber unnötige Vergleiche ein.
Wenn Abfragebereiche aneinanderstossen, geht immer was. Frei nach dem
Motto: Wenn etwas in einem Bereich ist, kann es nicht mehr im anderen
Bereich sein.
Karl Heinz Buchegger schrieb:> liefert genau die gleiche Logik, spart aber unnötige Vergleiche ein.> Wenn Abfragebereiche aneinanderstossen, geht immer was.
Würde mich aber nicht wundern, wenn der Compiler genau dasselbe selbst
herausfindet.
Mag jemand mal den ASM-Output für beide Varianten vergleichen?
OK.
Mein WinAVr auf dieser Maschine hier ist von der älteren Sorte
WinAVR20090313
Optimierung ist auf -Os
Schaun wir mal
1
#include<avr/io.h>
2
3
intmain()
4
{
5
uint8_ti;
6
uint8_tj;
7
8
i=PINB;
9
10
if(i<100)
11
j=80;
12
elseif(i>=100&&i<200)
13
j=100;
14
else
15
j=120;
16
17
PORTD=j;
18
}
daraus wird
1
i = PINB;
2
6c: 86 b3 in r24, 0x16 ; 22
3
4
if( i < 100 )
5
6e: 84 36 cpi r24, 0x64 ; 100
6
70: 10 f4 brcc .+4 ; 0x76 <main+0xa>
7
72: 80 e5 ldi r24, 0x50 ; 80
8
74: 06 c0 rjmp .+12 ; 0x82 <main+0x16>
9
j = 80;
10
else if( i >= 100 && i < 200 )
11
76: 84 56 subi r24, 0x64 ; 100
12
78: 84 36 cpi r24, 0x64 ; 100
13
7a: 10 f0 brcs .+4 ; 0x80 <main+0x14>
14
7c: 88 e7 ldi r24, 0x78 ; 120
15
7e: 01 c0 rjmp .+2 ; 0x82 <main+0x16>
16
80: 84 e6 ldi r24, 0x64 ; 100
17
j = 100;
18
else
19
j = 120;
20
21
PORTD = j;
22
82: 82 bb out 0x12, r24 ; 18
Nope. Er hats nicht gefunden. Ist aber trotzdem interssant, wie der
Compiler das gemacht hat. Ich hätte noch einen else-if Fall anhängen
sollen :-)
Gegentest
1
#include<avr/io.h>
2
3
intmain()
4
{
5
uint8_ti;
6
uint8_tj;
7
8
i=PINB;
9
10
if(i<100)
11
j=80;
12
elseif(i<200)
13
j=100;
14
else
15
j=120;
16
17
PORTD=j;
18
}
wird zu
1
i = PINB;
2
6c: 86 b3 in r24, 0x16 ; 22
3
4
if( i < 100 )
5
6e: 84 36 cpi r24, 0x64 ; 100
6
70: 10 f4 brcc .+4 ; 0x76 <main+0xa>
7
72: 80 e5 ldi r24, 0x50 ; 80
8
74: 05 c0 rjmp .+10 ; 0x80 <main+0x14>
9
j = 80;
10
else if( i < 200 )
11
76: 88 3c cpi r24, 0xC8 ; 200
12
78: 10 f0 brcs .+4 ; 0x7e <main+0x12>
13
7a: 88 e7 ldi r24, 0x78 ; 120
14
7c: 01 c0 rjmp .+2 ; 0x80 <main+0x14>
15
7e: 84 e6 ldi r24, 0x64 ; 100
16
j = 100;
17
else
18
j = 120;
19
20
PORTD = j;
21
80: 82 bb out 0x12, r24 ; 18
Aber ganz davon abgesehen, ist es ja auch fehleranfällig, wenn man
dieselbe Grenze im Code 2-mal angibt. Denn dann muss man selbst dafür
sorgen, dass beide Werte immer konsistent bleiben.
Peter Dannegger schrieb:> @Klaus Wachtler>> Da freut sich aber die CPU, daß sie richtig schön Zyklen verbraten darf> für die Division.
Hängt von der Optimierung ab
1
voidselect(unsignedchari)
2
{
3
externvoidfunc_1(void);
4
externvoidfunc_2(void);
5
6
i--;
7
i/=100;
8
9
if(i==0)
10
func_1();
11
if(i==1)
12
func_2();
13
}
Allerdings beachte man, daß hier nicht (i-1)/100 steht, was eine 16-Bit
Division bedeuten würde.
avr-gcc -mmcu=atmega8 -O2 schafft daraus:
1
select:
2
subi r24,lo8(-(-1))
3
ldi r25,lo8(41)
4
mul r24,r25
5
mov r24,r1
6
clr __zero_reg__
7
swap r24
8
andi r24,lo8(15)
9
breq .L5
10
cpi r24,lo8(1)
11
breq .L6
12
ret
13
.L5:
14
rjmp func_1
15
.L6:
16
rjmp func_2
Entfernt man dann noch das "char" so daß i vom Typ unsigned ist und man
es alse mit einer 16-Bit Division zu tun hat, ergibt sich
1
select:
2
movw r18,r24
3
subi r18,1
4
sbc r19,__zero_reg__
5
lsr r19
6
ror r18
7
lsr r19
8
ror r18
9
ldi r26,lo8(123)
10
ldi r27,lo8(20)
11
rcall __umulhisi3
12
lsr r25
13
ror r24
14
sbiw r24,0
15
breq .L5
16
sbiw r24,1
17
breq .L6
18
ret
19
.L5:
20
rjmp func_1
21
.L6:
22
rjmp func_2
D.h. auch hier wird keine Division ausgeführt, zumindest keine über
Schuldivision sondern über erweiternde Multipliketion mit dem
Reziproken.
In Beiden Fällen sehe ich nicht wirklich Raum für Optimierung.
...wie auch immer... wenn man auf eine Division verzichten kann wie im
vorliegenden Fall, dann sollte man es tun und if-else verwenden oder
switch-case.
Für das Beispiel ist das Frage des Geschmacks, der erzeugte Code wird eh
fast gleich sein.
Karl Heinz Buchegger schrieb:> OK.> Mein WinAVr auf dieser Maschine hier ist von der älteren Sorte> WinAVR20090313>> Optimierung ist auf -Os
Ich hab's mal durch future 4.7 durchgelassen. Der Code sieht gleich aus
(die Funktion heisst jetzt foo und gibt void zurück):
1
foo:
2
in r24,0x3
3
cpi r24,lo8(100)
4
brlo .L3
5
subi r24,lo8(-(-100))
6
cpi r24,lo8(100)
7
brsh .L4
8
ldi r24,lo8(100)
9
rjmp .L2
10
.L3:
11
ldi r24,lo8(80)
12
rjmp .L2
13
.L4:
14
ldi r24,lo8(120)
15
.L2:
16
out 0xb,r24
17
ret
Was neues lässt sich avr-gcc erst einfallen, wenn man Sprünge teurer
macht: zusätzlich mit -mbranch-cost=2 wird's 2 Instruktionen kürzer:
1
foo:
2
in r25,0x3
3
ldi r24,lo8(80)
4
cpi r25,lo8(100)
5
brlo .L2
6
subi r25,lo8(-(-100))
7
ldi r24,lo8(120)
8
cpi r25,lo8(100)
9
brsh .L2
10
ldi r24,lo8(100)
11
.L2:
12
out 0xb,r24
13
ret
Übrigens ist avr-gcc 4.7 die erste 4-er Version, die es schafft, für
mein Beispielprojekt kleineren Code zu erzeugen als 3.4.6
(WinAVR-20060421) :-))