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
voidpr1(void)
2
{
3
....
4
printf("PR1_O \n");
5
}
6
voidpr2(void)
7
{
8
.....
9
printf("PR2_O \n");
10
}
11
voidpr1U(void)
12
{
13
....
14
printf("PR1_U \n");
15
}
16
voidpr2U(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
voidRunFunction(U16Event)
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
voidInterpreter(U16wEvent)
51
{
52
U16wTable;
53
U8i;
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")
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.
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.
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..
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?
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.
@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.
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...
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.
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
voidRunFunction(U16Event)
2
{
3
RunEv[Event][wSysState]();
4
}
Fertig.
Wenn du es ein wenig expliziter haben willst, kannst du es auch so
schreiben
1
voidRunFunction(U16Event)
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.
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
voidpr1(void)
2
{
3
4
printf("PR1_O \n");
5
}
6
voidpr2(void)
7
{
8
9
printf("PR2_O \n");
10
}
11
voidpr1U(void)
12
{
13
14
printf("PR1_U \n");
15
}
16
voidpr2U(void)
17
{
18
19
printf("PR2_U \n");
20
}
21
22
23
24
25
26
27
intmain(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! */
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.