Forum: Compiler & IDEs 2-dim. Sprungtabelle in "C"


von yogy (Gast)


Lesenswert?

Hallo zusammen, vielleicht könnt Ihr mir etwas auf die Sprünge helfen.

Ich beschäftige mich z.Zt. mit dem AVRMega1280 und möchte ih in C 
programmieren. Dazu habe ich mir AVR Studio mit dem GCC Compiler 
installiert.

Ich selber bin kein studierter Programmierer oder Informatiker, sondern 
eher ein Analoging... Also bitte etwas Nachsicht.

Natürlich hab eich gegoogelt und auch hier gesucht ("Sprungtabelle") 
aber nichts rechtes gefunden.

Problem:

Ich habe vor ein paar Jahren mal eine C-SW für einen Texas TMS320 (mit 
Texas EW-Umgebung) modifizert, die den Dispatcher in der main-Schleife 
(Status- und Eventgesteuert) mit einer genialen Sprungateblle (2-dim 
Array) implementiert hatte. Dies erspart ressourcenfressende "Case" 
Anweisungen.

Kurz danach habe ich dieses Prinzip für "meine" Heizungssteuerung 
übernommen, die auf einem Motorola HC11 basiert. (IAR Compiler, uralt, 
unter MS-DOS.)

Nun möchte ich das auch für den AVR übernehmen.

Die Sprungtabelle sieht und die anzusprechenden Routinen sehen z.B. so 
aus:
1
void pr1(void)
2
{
3
....
4
printf("PR1_O \n");
5
}
6
void pr2(void)
7
{
8
.....
9
printf("PR2_O \n");
10
}
11
void pr1U(void)
12
{
13
....
14
printf("PR1_U \n");
15
}
16
void pr2U(void)
17
{
18
.....
19
printf("PR2_U \n");
20
}
21
22
/* Tabelle */
23
24
void  (*RunEv [2][2] ) (void) PROGMEM =
25
{
26
{pr1,pr2},
27
{pr1U,pr2U}};
28
29
/* Die Spalten werden durch Systemstatus wSysState ausgewählt, die Zeilen durch Event  */
30
31
/* Die richtige Routine wird aufgerufen durch: */
32
33
void RunFunction(U16 Event)
34
{
35
  RunEv[Event][wSysState];
36
}
37
38
39
/* Die Hauptprogrammschleife sieht z.B. folgendermaßen aus: */
40
41
    while( 1 ) {
42
  _delay_ms(10);
43
  wSysState=1;  /* global */
44
  wEvent=2;  /* global */
45
  Interpreter(wEvent);
46
    }
47
48
/* Der "Interpreter:" */
49
50
void Interpreter(U16 wEvent)
51
{
52
U16 wTable;
53
U8  i;
54
55
.......
56
  wTable=1;
57
  for (i=1;i<=2;i++) {
58
    if (wEvent & wTable) {
59
      RunFunction(i);
60
    }
61
    wTable <<= 1;
62
  }
63
}

Nur das ganze funzt nicht mit dem GCC. Die auzuspringenden Routine, hier 
PR1U, wird nicht aufgerufen. Please Help. Danke.
(Ich hoffe, das Posting ist "konform")

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

RunEv ist im Flash angelegt, was durchaus sinnvoll ist wenn sich die 
Tabelle nicht ändert. Der Zugriff auf via RunEv[..][..] macht aber einen 
RAM-Zugriff.

Schau dir mal die pgm_* Makros in der avr-libc an.

von Karl H. (kbuchegg)


Lesenswert?

Alternativ zu Johanns Antwort:

Du kannst auch einfach das PROGMEM hier wegnehmen
1
void  (*RunEv [2][2] ) (void) PROGMEM =
2
{
3
{pr1,pr2},
4
{pr1U,pr2U}};

dann liegt die Tabelle wieder im SRAM und du kannst erst mal ohne 
irgendwelche Umwege darauf zugreifen.
Wenn du Speicher genug hast, musst du dir nicht noch extra Prügel 
zwischen die Beine werfen. Zumindest nicht am Anfang. Da hast du mit 
anderen Dingen erst mal mehr zu tun:

void  (*RunEv [2][2] ) (void)  =  ....

  wTable=1;
  for (i=1;i<=2;i++) {


zb dass Array Indizierung in C bei 0 beginnt.
RunEv hat kein Element [2][1]
Es hat Elemente  [0][0], [0][1], [1][0] und [1][1].
Irgendetwas mit Index 2 existiert nicht.

von yogy (Gast)


Lesenswert?

Das ging schnell, danke für die Antwort.

Daß mit dem Index "2" ist schon klar, im Testprogramm ist das das LSB 
vom Sekundenzähler, habe hier nur falsch reingeschrieben. Asche auf mein 
Haupt.

Zum Thema: Im Ram wollte ich die Tabelle "eigentlich" nicht haben, werde 
das jetzt aber mal testen..

von yogy (Gast)


Lesenswert?

Mach ich, danke für die Hilfe.

von yogy (Gast)


Lesenswert?

Der Versuch mit den pgm_ Macros hat (natürlich) auf die Schnelle  nicht 
gefunzt. Habe ich also für später aufgehoben, in der Hoffnung, es würde 
mit der RAM-Lösung eher gehen.

Aber nein. Nichts wird angesprungen. (Als event und state geneireie ich 
alle zulässigen Kombinationen)

Ich habe daraufhin die RunFunktion durch eine Ausgabefunktion ersetzt, 
die mir die jeweilige Adresse des Arrays (im Ram) und deren Inhalt 
ausgibt.

Dabei ist der Inhalt jeweils genau die Hälfte des Wertes, den ich in der 
Linkermap für die anzuspringende Routine finde.

Ist das okay bzw. hängt das mit der Wortadressierung des ROMs (Flash) 
zusammen?

Auf jeden Fall wird die anzuspringende Routine nicht angesprungen, also 
die Run Function versagt....  Was mache ich falsch?

von DerAlbi (Gast)


Lesenswert?

Also angenommen du hast Funktionsadressen im Array..

aber hast du schonmal einen Funktionsaufruf in C gesehen der hne () 
stattfindet??

RunEv[Event][wSysState] ist doch bullshit. Das lädt allesfalls die 
Spungadresse und die wird dann verworfen, aber anspringen tut die 
wirklich nicht. Wen das passieren würde, wärs ein Compilerbug.

das ist wie als würdest du

void bla()
{
 tuwas = machich;
}

int main()
{
 bla; //denkste, dass bla aufgerufen wird????
 blah(); //oder bevorzugste dann noch die klammern.
}

Viel Spaß noch.

von yogy (Gast)


Lesenswert?

@derAlbi


Das ist so nicht richtig, denn das ganze funktioniert beim TMS320 und 
auch beim HC11 (IAR-Compiler).

Das Prinzip ist, daß die Startadresse (Einsprung, ProgramCounter) in dem 
Array liegt. RunFunktion selber ruft keine Funktion auf, sondern stellt 
lediglich eine Adresse (word, Startadresse der Routine) bereit, die 
unmittelbar in den Programmcounter geladen wird, also ohne 
Functions/Subroutine-Aufruf.

In Assembler (imaginärer Proizessor) sähe das so aus:

LDP <Adresse Aus Array> (Lade Adrese umittelbar in den Programmcounter)

Das ist schon alles.

Beim Rücksprung aus dem so aufgerufebnen Subroutine/Function wird vom 
Stack implizit die ProgrammAdresse des Interpreters nach RunFunction 
aufgerufen.

Diese Art des Dispatchens befreit den Programmierer von elend langen und 
unübersichtlichen CASE/SELECT Strukturen, die zudem zuviel Speicher 
verbrauchen.

Und das ganze ist aucn bekannt hier:

Beitrag "Sprungtabelle in Assembler"

Allerdings funktioniert diese eindimensionale Tabelle bei mir auch 
nicht.

von yogy (Gast)


Lesenswert?

So, mögliche Lösung:

Die Adresse kann ich sowohl bei der RAM Version also auch bei der ROM 
Verson (PROGMEM) nun feststellen und auslesen.

Ich überlege nun, den Sprung mit InLine Assembler zu realisieren, 
benötioge dazu aber ein frei verfügbares Register-Paar. Muß mal die Doku 
lesen...

Dann würde ich die Adresse aus dem C-Programm (liegt ja wohl in 
irgendwelchen Registern?) mittels 2 mal push  auf den Stack bringen und 
dann mittels des ret Befehles den Programmcounter laden.

Ich werde berichten...

von Karl H. (kbuchegg)


Lesenswert?

yogy schrieb:


> Das ist so nicht richtig,

Doch, das ist richtig.

> denn das ganze funktioniert beim TMS320 und
> auch beim HC11 (IAR-Compiler).

Das mag sein, dann sind diese Compiler fehlerhaft.

> Das Prinzip ist, daß die Startadresse (Einsprung, ProgramCounter) in dem
> Array liegt. RunFunktion selber ruft keine Funktion auf, sondern stellt
> lediglich eine Adresse (word, Startadresse der Routine) bereit, die
> unmittelbar in den Programmcounter geladen wird, also ohne
> Functions/Subroutine-Aufruf.

Das ist Bullshit
Wenn du eine Funktion aufrufen willst, dann sind in C die () 
obligatorisch.

> Beim Rücksprung aus dem so aufgerufebnen Subroutine/Function wird vom
> Stack implizit die ProgrammAdresse des Interpreters nach RunFunction
> aufgerufen.

In C gibt es keinen Stack.
Die konzeptionelle C-Maschine auf der die Sprachdefinition beruht, kennt 
diese Konzepte alle nicht. Und die Sprache C ist auch nicht so 
definiert, dass diese Konzepte zwingend notwendig sind.

> Allerdings funktioniert diese eindimensionale Tabelle bei mir auch
> nicht.

Weil du keine Funktion aufrufst, sondern nur deren Adresse benutzt aber 
nichts damit machst.

von Karl H. (kbuchegg)


Lesenswert?

yogy schrieb:

> Ich überlege nun, den Sprung mit InLine Assembler zu realisieren,

So ein QUatsch.

Programmier das einfach in C, so wie man eben eine Funktion aufruft (und 
nicht einfach nur die Startadresse davon feststellt) und gut ists.


> irgendwelchen Registern?) mittels 2 mal push  auf den Stack bringen und
> dann mittels des ret Befehles den Programmcounter laden.

Programmier einfach nur so, wie das in C funktioniert und so vorgesehen 
ist. Das reicht dann schon. Mehr brauchst du nicht tun.
1
void RunFunction(U16 Event)
2
{
3
  RunEv[Event][wSysState]();
4
}

Fertig.

Wenn du es ein wenig expliziter haben willst, kannst du es auch so 
schreiben
1
void RunFunction(U16 Event)
2
{
3
  (*RunEv[Event][wSysState])();
4
}

Dann wird etwas klarer, dass es sich um einen Pointer handelt, den du 
derferenzierst um an das 'Funktionsobjekt' zu kommen, welches dann 
ausgeführt wird.

von yogy (Gast)


Lesenswert?

So, Problem gelöst. Dank eurer Mithilfe, insb. von Karl-Heinz

Aber zunächst ein paar Anmerkungen:

> Das mag sein, dann sind diese Compiler fehlerhaft.

Das mag sein, ich bin kein Compilerbauer und kann das daher nicht 
beurteilen. IAR war mal der Porsche unter den Crosscompilern...

> Das ist Bullshit
> Wenn du eine Funktion aufrufen willst, dann sind in C die ()
> obligatorisch.

Jein. Es hat zuimindest (wg. Compilerfehler, s.o.) funktioniert... der 
GCC benötigt jedoch die (), so konnte ich dann die Funktion auch 
aufrufen (s.u.)

> In C gibt es keinen Stack.

Das ist/war wohl ein Mißverständnis. C Hat zwar keine Stack-Befehle, 
nutzt jedoch den Systemstack. Sonst könnte es ja nicht funzen. Siehe 
Assembler-Zwischenlistiungs mit call's und ret's. Auch lokale Variablen, 
soweit sie nicht in Registern Platzfinden, kommen auf einen bzw. den 
Stack, falls nicht mehrere existieren resp. benutzt werden (Der gute 
alte Motorola 6809 hatte dazu zwei Stackpointer...). Aber wie gesagt, 
Mißverständnis, ich habe mich wohl falsch ausgedrückt.

> Programmier einfach nur so, wie das in C funktioniert und so vorgesehen
> ist. Das reicht dann schon. Mehr brauchst du nicht tun.

Ich habe das mit Assemblerroutinen innerhalb von C früher schon 
realisiert, das funzt wunderbar, solange der Compiler nicht alles 
wegoptimiert und solange die Übergaberegister festgelegt sind. Ich habe 
es hier versucht und bin (natürlich) gescheitert.

Mit dem "richtigen" Funktionsaufruf (mit ()) hat es dann, zumindest bei 
der RAM-Tabelle auch geklappt. Für die ROM-Tabelle war ein Erweiterung 
notwendig, sonst gab es Fehlermeldungen..

Das funktionierende Listing:
1
void pr1(void)
2
{
3
4
printf("PR1_O \n");
5
}
6
void pr2(void)
7
{
8
9
printf("PR2_O \n");
10
}
11
void pr1U(void)
12
{
13
14
printf("PR1_U \n");
15
}
16
void pr2U(void)
17
{
18
19
printf("PR2_U \n");
20
}
21
22
23
24
25
26
27
int main (void)
28
{
29
  ..... /*  Initialisierungen */
30
   
31
    while( 1 ) {                // Endlosschleife
32
  _delay_ms(500);
33
    ......
34
35
  wSysState=((CL_SEC >> 7) & 1);
36
  Interpreter(2); /* wird ja bitweise verarbeitet! */
37
  
38
  }
39
    return (0);
40
}
41
42
/************************************************************
43
*  Sprungtabelle/state-event-Array *************************************************************/
44
45
/* Sprungtabelle im ROM */
46
void  (*RunEv [2][2] ) (void) PROGMEM  =   
47
{
48
{pr1,pr2},
49
{pr1U,pr2U}};
50
/* dazu notwendige Hilfsspeicherstelle */
51
void   (*StartAddress) (void)  = 0xffff;
52
53
/* Sprungtabelle im RAM */
54
void  (*RunEvRAM [2][2] ) (void)   =   
55
{
56
{pr1,pr2},
57
{pr1U,pr2U}};
58
59
60
/************************************************************
61
RunFunction                
62
************************************************************/
63
void RunFunction(U16 Event)
64
{
65
U16 ia;
66
  csabs(2,1); /* setzr cursor*/
67
68
/* Version mit Tabelle im ROM/ProgMemory */
69
  StartAddress = pgm_read_word (&(RunEv[wSysState][Event]));
70
  (*StartAddress)();
71
72
/*  Version mit Tabelle im RAM / Arbeitsspeicher */
73
  (*RunEvRAM[wSysState][Event])(); 
74
}
75
76
/**********************************************************
77
*    *
78
*  Interpreter  *
79
*    *
80
**********************************************************/
81
void Interpreter(U16 wEvent)
82
{
83
U16 wMask;
84
U8  i;
85
86
  wMask=1;
87
  for (i=0;i<2;i++) {
88
    if (wEvent & wMask) {
89
      RunFunction(i);
90
    }
91
    wMask <<= 1;
92
  }
93
}

Danke nochmal, jetzt kann ich weiterspielen...

von Karl H. (kbuchegg)


Lesenswert?

yogy schrieb:

>> Das ist Bullshit
>> Wenn du eine Funktion aufrufen willst, dann sind in C die ()
>> obligatorisch.
>
> Jein. Es hat zuimindest (wg. Compilerfehler, s.o.) funktioniert... der
> GCC benötigt jedoch die (), so konnte ich dann die Funktion auch
> aufrufen (s.u.)

Du musst strikt trennen zwischen dem was der Sprachstandard zum Thema zu 
sagen hat, und dem was einzelne Compiler daraus machen.

Weder IAR noch der TMS Compiler definieren, was C ist und was nicht, 
sondern der C-Standard.

Und dieses Dokument definiert die Dinge nun mal so. Das hat mit dem GCC 
gar nichts zu tun.

>
>> In C gibt es keinen Stack.
>
> Das ist/war wohl ein Mißverständnis. C Hat zwar keine Stack-Befehle,
> nutzt jedoch den Systemstack.


Genau davon rede ich.
Ist völlig uninteressant - aus Sicht des Sprachstandards.
Das Dokument, welches die Sprache C beschreibt, setzt keinen Stack 
voraus. C - als abstrakte Maschine hat keinen Stack. Das ein Stack eine 
mögliche Implementierung ist, mit der man den vorgeschriebenen 
Mechanismus eines Funktionsaufrufs implementieren kann, ist schon klar. 
Aber es ist keineswegs der einzige mögliche Weg. Das Dokument, welches 
die Sprache definiert, schreibt gewisse Funktionalität vor und nicht wie 
diese Funktionalität zu erreichen ist. Die Funktionalität lautet: Es 
muss möglich sein eine Funktion aufzurufen und nach Beendigung der 
Funktion kehrt der Programmfluss wieder an die Stelle des Aufrufs 
zurück. Wie diese Funktionalität erreicht werden kann, wird nicht 
definiert. Das kann ein Stack sein, muss es aber nicht.

Du verwechselst hier bestimmte Implementierungen der Sprache C mit dem, 
was tatsächlich von der genormten Sprache C gefordert wird, was im 
Normungsdokument steht. Und im Zweifelsfall gilt immer dieses Dokument 
und nicht das, was ein bestimmter Compiler tatsächlich implementiert.

Wenn ich (und andere) von C sprechen, dann sprechen wir immer davon, was 
in erster Linie das Normungsdokument zum Thema zu sagen hat.

Und auf eines kannst du eine absolut sichere Bank wetten:
Wenn du zum Erreichen von etwas Bestimmten auf Assembler runter musst, 
dann bist du weit, weit, weit weg von jeglicher C-Sprachdefinition. Das 
heißt aber auch im Umkehrschluss: Wenn du weißt, dass etwas in C 
prinzipiell möglich sein müsste, dann kann niemals Assembler eine Lösung 
dafür sein.

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.