Forum: PC-Programmierung Optimale Auswertung von Rückgabewerten


von Martin (Gast)


Lesenswert?

Hallo,

ich möchte die Antwort eines Touchscreens (über den I²C-Bus) mit einem
AtMega88 auswerten. Ich verwende zum Programmieren den kostenlosen
AVR-GCC-Compiler.

Der Touchscreen liefert je nach betätigtem Button einen Code zum
Prozessor. Die Auswertung mache ich bisher mit IF-Anweisungen:

if(code == 0x11)
    {
     X= 111;
     .....
    }
if (code == 0x12)
    {
     X= 122;
     .....
    }
…

Bisher habe ich nur 4 Auswertungen programmiert. Nun muß ich allerdings
ungefähr 100 Rückgabecodes auswerten. Das benötigt im Extremfall recht
lange, wenn die Bedingung erst weit unten zutrifft. Mit der
Switch-Anweisung in C ist es nicht viel besser. Gibt es eine bessere
Variante?

Ich könnte mir folgendes Vorstellen:

Der Code vom Touchscreen wird an einer Stelle zum Program-Counter
addiert. An der entsprechenden Stelle des addierten Programmcounters
steht dann eine entsprechende Verzweigung zum ausführenden Teil (quasi
eine GOTO-Anweisung). Geht so was? Wie sieht so was programmtechnisch
aus? Am besten in C. Kann mir jemand weiter helfen und Quellbeispiele
aufzeigen?

Habe bisher nicht all zu viel praktische Erfahrungen mit so was....

Vielen Dank im Voraus.

Martin

von Rolf Magnus (Gast)


Lesenswert?

> Mit der Switch-Anweisung in C ist es nicht viel besser.

Hmm, seltsam.

> Der Code vom Touchscreen wird an einer Stelle zum Program-Counter
> addiert. An der entsprechenden Stelle des addierten
> Programmcounters steht dann eine entsprechende Verzweigung zum
> ausführenden Teil (quasi eine GOTO-Anweisung). Geht so was?

Das ist so etwa das, was switch/case eigentlich machen sollte.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

> > Der Code vom Touchscreen wird an einer Stelle zum Program-Counter
> > addiert. An der entsprechenden Stelle des addierten
> > Programmcounters steht dann eine entsprechende Verzweigung zum
> > ausführenden Teil (quasi eine GOTO-Anweisung). Geht so was?
>
> Das ist so etwa das, was switch/case eigentlich machen sollte.

Das ist eher ein Array von Funktionspointern. Das ist von der
Performance her nicht zu überbieten.

Der Touchscreen liefert einen 8-Bit-Code (so sieht das jedenfalls aus),
also muss das Array 256 Einträge haben. Alle davon werden mit Pointern
auf die jeweils aufzurufende Funktion gefüllt; dabei kann die gleiche
Funktion auch mehreren Codes zugeordnet werden, was beispielsweise
sinnvoll ist, um ungültige Codes mit einer Fehlerfunktion abzufangen.


switch/case macht explizite Vergleiche mit angegebenen Werten und ist
so nur eine elegantere Schreibweise für if/else-Bandwürmer.
Bis der letzte Eintrag im "Bandwurm" gefunden wird, dauert es
deutlich länger als bis zum ersten Eintrag.

Ein Funktionspointerarray ist für alle Eingangscodes gleichschnell,
belegt aber -je nach verwendetem Prozessor- 512 oder gar 1024 Bytes
Speicher. Allerdings ist der für 256 Varianten eines
switch/case-Konstruktes erzeugte Code bedeutend größer - mit nur zwei
bzw. vier Bytes je Variante ist kein Vergleich und Sprung
hinzubekommen.

Sofern der Eingangscode eine Bereichsbegrenzung zulässt (Code nie
größer als 127 o.ä.), kann natürlich das Array entsprechend verkleinert
werden.

von Martin (Gast)


Lesenswert?

Hallo Rufus,

ja, das Touchscreendisplay liefert einen 8-Bit-Code zurück. Den Code
vergebe ich selber in der Displaysoftware (intelligentes Display von
Electronic Assembls (EA DIP 240-7). Als Rückgabewerte könnte ich Werte
von 1 bis 127 vergeben. Somit wären nur 7 Bit zu vergleichen.

Wie wird so ein Funktionspointerarray in C geschrieben? Kannst Du mir
den Quellcode hier angeben? Hab da absolut keine Vorstellungen.

Ich habe mir noch überlegt, ob ich alternativ Verzweigungen mache und
jedes Bit des Codes nacheinander einzeln abfrage, ob es 1 oder 0 ist.
Somit komme ich nach 8 Abfragen (8 Bit) zu der gewünschten Stelle. Wird
aber im C-Code sicherlich verdammt unübersichtlich.

Martin

von Rolf Magnus (Gast)


Lesenswert?

> Das ist so etwa das, was switch/case eigentlich machen sollte.
>
> Das ist eher ein Array von Funktionspointern. Das ist von der
> Performance her nicht zu überbieten.

Doch, ist es, und zwar mit switch/case. Über einen Funktionspointer muß
die Funktion erst aufgerufen werden, mit Sicherung von Registern auf dem
Stack, ggf. Parameterübergabe u.s.w., nachher wieder zurücklesen und das
ret. Das kostet recht viel Zeit. switch/case arbeitet oft (wenn die
Werte es zulassen) mit einer Sprungtabelle oder manchmal auch mit einer
binären Suche, wenn die Werte zu weit verteilt sind.

> switch/case macht explizite Vergleiche mit angegebenen Werten und
> ist so nur eine elegantere Schreibweise für if/else-Bandwürmer.

Wie kommst du darauf?

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Das ist nicht sonderlich kompliziert.

Alle Funktionen müssen auf die gleiche Art und Weise aufgerufen werden
können (gleiche Argumente, gleicher Rückgabewert).

Angenommen, eine Deiner Funktionen hat folgenden Prototypen:

  int funktion(int argument);

Dann sieht ein Funktionspointer auf eine solche Funktion so aus:

  int (*pfunktion)(int argument);

Die Zuweisung der Funktionsadresse zum Pointer funktioniert so:

  pfunktion = funktion;

Und das Dereferenzieren des Pointers (also das Aufrufen der Funktion)
geschieht so:

  int ergebnis;

  ergebnis = pfunktion(4);


Ein Array solcher Funktionspointer wird so deklariert:

  int (*arrayfunktion[4])(int argument);

Initialisiert wird so ein Array auf diese Art und Weise:

  int (*arrayfunktion[4])(int argument) = { funktion1, funktion2,
funktion3, funktion4 };

Und der Aufruf einer Funktion aus diesem Array geht so:

  ergebnis = arrayfunktion[1](2);


Viel Erfolg!

von Stefan Kleinwort (Gast)


Lesenswert?

Habe sowas bei meiner Hausbus-Software verwendet, den kompletten Source
findest Du in dem ellenlangen Hausbus-Thread oder ich schicke ihn Dir
bei Bedarf. Für den AVR ist es etwas komplexer, wegen dem
PROGMEM-Problem:

typedef void (*init_function_typ)(void);

void init_nothing(void){
  ...
}

void init_taster(void){
 ...
}

// ... etc.


// Definition der Tabelle mit den Adressen der Funktionen:

PROGMEM init_function_typ init_function_table[typ_anz+1] = {
  init_nothing,   // Aufruf dieser Funktion bei Wert = 0
  init_nothing,   // 1
  init_taster,    // 2, etc.
  init_taster,
  init_taster,
  init_relais,
  init_jalousie,
  init_dimmer
};


  for(i=0; i<module_anz;i++){
    init_function_ptr = (init_function_typ)
pgm_read_word(&init_function_table[i]);
      init_function_ptr();
  }


Bei der Tabelle mit den Funktionen siehst Du schön, dass für mehrere
Werte sehr einfach dieselbe Funktion verwendet werden kann.
In Deinem Anwendungsfall: unbedingt vor dem Aufruf den Tabellenoffset
auf Gültigkeit testen (bei Dir: 127), sonst springst Du ins Nirvana...

Viele Grüße, Stefan

von Stefan Kleinwort (Gast)


Lesenswert?

Mist, dritter :-((

Stefan

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Zum Code von Stefan sei noch angemerkt, daß der eine AVR-Spezialität
enthält.
Das Pointerarray ist durch Verwendung des Präfixes "PROGMEM" im
Flash-ROM abgelegt, und um einen darin abgelegten Pointer verwenden zu
können, muss der erst in eine "normale" (im RAM liegende)
Pointervariable kopiert werden (was mit "pgm_read_word" geschieht),
bevor sie aufgerufen werden kann.

C auf einem AVR hat halt seinen ganz besonderen Nachgeschmack.

von Martin (Gast)


Angehängte Dateien:

Lesenswert?

Hallo Stefan,

vielen Dank für den Quellcode. Leider funktioniert was nicht. Es kommen
folgende Meldungen:

avr-gcc -g -Wall -O0 -mmcu=atmega88    -c -o master.o master.c
In file included from master.c:47:
test.c:28: error: syntax error before "init_function_typ"
test.c:41: error: parse error before "for"
test.c:44: error: parse error before '&' token
test.c:44: warning: type defaults to `int' in declaration of
`pgm_read_word'
test.c:44: warning: data definition has no type or storage class
test.c:45: warning: type defaults to `int' in declaration of
`init_function_ptr'
test.c:45: warning: data definition has no type or storage class
test.c:46: error: parse error before '}' token

Hier der Quellcode (auch als Datei im Anhang):

typedef void (*init_function_typ)(void);    // ???


  // hier die Funktionen, die bei entsprechendem Rückgabecode ausgeführt
werden sollen
void init_nothing(void)
  {
  test2 = 5;
  return;
  }

void init_taster(void)
  {
   test2 = 10;
   return;
  }

void init_relais(void)
  {
   test2 = 15;
   return;
  }



// Definition der Tabelle mit den Adressen der Funktionen:

PROGMEM init_function_typ init_function_table[5] = {
  init_nothing,   // Aufruf dieser Funktion bei Wert = 0
  init_nothing,   // 1
  init_taster,    // 2
  init_taster,    // 3
  init_relais    // 4
   // => müssen die nicht "init_xxx();" heißen (Klammer auf, Klammer
zu und Strichpunkt?
};

  // Deklaration der verwendeten Variablen
  unsigned char i;        // Zähler
  unsigned char module_anz = 3;  // max. Wert des Rückgabewertes (127)?

  for(i=0; i<module_anz;i++)
  {
  init_function_ptr = (init_function_typ);  // ???
  pgm_read_word(&init_function_table[i]);  // ???
    init_function_ptr();            // ???
  }

Das mit Zeigern habe ich noch nie so richtig verstanden. Kannst Du den
Quellcode korrigieren und eventuell Kommentare hinzufügen, was Du in
der entsprechenden Zeile machst?

von Martin (Gast)


Lesenswert?

.. hier noch die verwendeten H-Files vom Compiler. Vielleicht muß ja
noch was ergänzt werden...

#include <avr/delay.h>
#include <inttypes.h>
#include <string.h>
#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/interrupt.h>
#include <avr/signal.h>
#include <avr/iomx8.h>

von Stefan Kleinwort (Gast)


Lesenswert?

Das ist aber nicht das ganze test.c File?

>> test.c:28: error: syntax error before "init_function_typ"

init_f_typ steht in Deinem Code in Zeile 1, nicht in Zeile 28 ?

  // Deklaration der verwendeten Variablen
  unsigned char i;        // Zähler
  unsigned char module_anz = 3;  // max. Wert des Rückgabewertes (127)?

  for(i=0; i<module_anz;i++)
  {
  init_function_ptr = (init_function_typ);  // ???
  pgm_read_word(&init_function_table[i]);  // ???
    init_function_ptr();            // ???
  }

-> Code ausserhalb einer Funktion !?

Welche WINAVR-Version benutzt Du?

Viele Grüße, Stefan

von Rolf Magnus (Gast)


Lesenswert?

Warum zieht eigentlich keiner mehr das switch/case in Betracht. Ich
hab's grad nochmal ausprobiert, und mein avr-gcc hat bis 8 case-Werten
die Implementation als if/else-Kaskade gemacht, danach ist er auf die
Sprungtabelle gegangen.
Und bei so einfachem Code wie "test2 = 10;" wäre die um ein
Vielfaches schneller und kompakter als eine Horde von kurzen Funktionen
und deren Aufruf über ein Array aus Zeigern.

von Martin (Gast)


Lesenswert?

die angehängte Datei der vorigen Mail ist das komplette test.c-File.

Zeile 28 die er anmeckert ist folgende:
PROGMEM init_function_typ init_function_table[5] = {


Wie finde ich die WINAVR-Version raus? Im Programmers Notepad [WinAVR]
kann man die Info nicht abfragen. Das einzigtse was ich auf die
schnelle gefunden habe ict das Datum der EXE Datei:
Die Datei "avr-gcc.exe" ist vom 11.02.2005

Gruß
Martin

von Martin (Gast)


Lesenswert?

@ Rolf Magnus

... im realen Programm wird schon etwas mehr gemacht, als nur "test2 =
10;".

Ich werde das mit switch/case mal ausprobieren. Muß erst noch mal kurz
ins C-Buch schauen, wie das genau geht....

von Martin (Gast)


Lesenswert?

Hab nun das Ganze mit switch / case getestet. Das gibt bei Switch ein
Latte ASM-Befehle, aber die Zeiten für die Ausführung scheinen
akzeptabel zu sein. Hier die Ausführungszeiten zum Finden der Einträge
(hab mal 32 Cases mit jeweils einer Zuweisung gemacht):

1.  Case-Eintrag: 17,1µs
16. Case-Eintrag: 8,81 µs
32. Case-Eintrag: 18,72 µs

Ich probiers mal nun so. Das kapier ich zumindest. Wie gesagt, mit
Zeigern habe ich so meine Probleme...

von Stefan Kleinwort (Gast)


Lesenswert?

@Martin,

#include <avr/io.h>
#include <inttypes.h>
#include <avr/pgmspace.h>

Probier mal diese includes, zumindest das pgmspace.h.
Das wird die meisten Fehler beheben. pgmspace.h brauchst Du, um
konstante Daten im Flash anzulegen (das macht das PROGMEM) und auch
wieder auszulesen (pgm_read_word). Das ist eine kleine Unschönheit des
ATmega ...

Als zweites steht Dein Code etwas nackt da. Vor das
// Deklaration der verwendeten Variablen
sollte noch:

void main(void){

Deine WINAVR ist die aktuelle.


@Rolf:

switch ist sicher sehr oft passend. Ich habe mein Codebeispiel
eigendlich nur deshalb reingestellt, weil explizit danach gefragt
wurde. In meiner Applikation verwende ich es, weil die aufgerufenen
Funktionen in ganz unterschiedlichen Dateien stehen (das Ganze ist ein
rudimentärer Scheduler).
Bei meinen Versuchen mit gcc und switch hat er nie eine Tabelle
angelegt - vielleicht liegt das daran, dass meine Einträge nicht bei 0,
sondern meistens erst bei den ASCII-Zeichen "0" oder "A" anfangen.
Unter welchen Optimierungs-Einstellungen hast Du getestet?

Gruß, Stefan

von Rolf Magnus (Gast)


Lesenswert?

Ich hab mit -O3 und -Os getestet.
Der Testcode war folgender:
1
#include <avr/io.h>
2
3
volatile uint8_t X;
4
5
void func(uint8_t code)
6
{
7
    switch (code)
8
    {
9
        case 0x1:
10
            X = 12;
11
            break;
12
        case 0x02:
13
            X = 15;
14
            break;
15
        case 0x3:
16
            X= 111;
17
            break;
18
        case 0x4:
19
            X = 124;
20
            break;
21
        case 0x5:
22
            X = 126;
23
            break;
24
        case 0x6:
25
            X = 128;
26
            break;
27
        case 0x7:
28
            X = 122;
29
            break;
30
        case 0x8:
31
            X = 122;
32
            break;
33
        case 0x120:
34
            X= 10;
35
    }
36
}
37
38
int main()
39
{
40
    func(PORTB);
41
}

PORTB und eine volatile-Variable hab ich verwendet, damit der Compiler
nicht auf die Idee kommt, einfach alles wegzuoptimieren.

Ich hab hier bei 1 angefangen und extra auch einen Wert, der aus der
Reihe tanzt, mit aufgenommen, um zu sehen, was er draus macht. Der
Anfangswert spielt keine große Rolle. Er wird subtrahiert. Wichtiger
ist eher die Verteilung der Werte. Wenn der eine oder andere Ausreißer
drin ist, wird der wohl über eine Branch ausgefiltert. Der Rest wird
trotzdem per Sprungtabelle gemacht.

Der relevante Teil des Code, den gcc dabei generiert hat, sieht bei -Os
so aus:
1
        cpi r30,8
2
        cpc r31,__zero_reg__
3
        brsh .L13
4
        subi r30,lo8(-(pm(.L11)))
5
        sbci r31,hi8(-(pm(.L11)))
6
        ijmp
7
        .data
8
        .section .progmem.gcc_sw_table, "ax", @progbits
9
        .p2align 1
10
.L11:
11
        .data
12
        .section .progmem.gcc_sw_table, "ax", @progbits
13
        .p2align 1
14
        rjmp .L3
15
        rjmp .L4
16
        rjmp .L5
17
        rjmp .L6
18
        rjmp .L7
19
        rjmp .L8
20
        rjmp .L9
21
        rjmp .L10
22
        .text
23
.L3:
24
        ldi r24,lo8(12)
25
        rjmp .L14
26
.L4:
27
        ldi r24,lo8(15)
28
        rjmp .L14
29
.L5:
30
        ldi r24,lo8(111)
31
        rjmp .L14
32
.L6:
33
        ldi r24,lo8(124)
34
        rjmp .L14
35
.L7:
36
        ldi r24,lo8(126)
37
        rjmp .L14
38
.L8:
39
        ldi r24,lo8(-128)
40
        rjmp .L14
41
.L9:
42
        ldi r24,lo8(122)
43
.L14:
44
        sts X,r24
45
        ret
46
.L10:
47
        ldi r24,lo8(122)
48
        sts X,r24
49
.L13:
50
        ret

Und das ist unverkennbar eine Sprungtabelle. Der generierte Code könnte
zwar evtl. noch besser sein, aber besser als die
Funktionszeiger-Variante müßte er wohl dennoch sein.
Meine Aussage, es sei um ein Vielfaches besser, war aber vermutlich
etwas übertrieben.

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.