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
> 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.
> > 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.
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
> 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?
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!
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
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.
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?
.. 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>
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
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.
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
@ 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....
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...
@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
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.