Hi,
folgende Problematik mit dem WinAVR:
In einem (etwas) gewachsenen Controller-Projekt (in Assembler) möchte
ich erstmals einige Funktionen in C schreiben. Nu habe ich gelesen, das
GCC-Assembler ist nicht so ganz identisch mit dem AVRStudio-Assembler.
Die Assemblerfiles direkt im C-Projekt einzubinden, scheitert zumindest
erst einmal an den Präprozessordirektiven, ob Makros & Co funktionieren,
hab ich noch gar nicht ausprobiert.
Frage also: Gibt es irgendeine (einfache!!!) Lösung (über Linker, Tools
oder sonstewas), das Pferd von hinten aufzuzäumen und aus meinen wenigen
tausend Zeilen bestehendem Assemblercode eine C-Routine aufzurufen.
Und wenn ja (überleg, überleg), komme ich dann aus der C-Routine noch an
die im .dseg definierten Variablen ran?
Gruß Horst
Horst S. schrieb:> Frage also: Gibt es irgendeine (einfache!!!) Lösung (über Linker, Tools> oder sonstewas), das Pferd von hinten aufzuzäumen und aus meinen wenigen> tausend Zeilen bestehendem Assemblercode eine C-Routine aufzurufen.
ich denke nicht.
Es ist ja nicht nur die Funktion. GCC geht ja auch davon aus das gewisse
Inhalte in einige Register vorhanden sind. Diese Konvention müsstest du
vorher in deinen Projekt auch umsetzten.
Eventuell werden ja aus deinen tausend Zeilen ASM nur 100 Zeilen C code,
dann es doch schnell umgeschrieben.
Horst S. schrieb:> Frage also: Gibt es irgendeine (einfache!!!)
Nein.
Damit in C Funktionen laufen können, braucht es eine Infrastruktur. Die
hast du aber nicht in deinem Assembler Original.
Was maximal, vielleicht gehen könnte.
Das Pferd von vorne aufzäumen. Das ganze Projekt als C Projekt
generieren, aus dem bisherigen Assembler Code die Basisinitialisierung
geeignet rausstreichen (Stackpointer, Interrupt-Vektoren etc.) und von
main() aus dann den bisherigen Assemblerteil aufrufen.
Aber ich denke, im Endeffekt ist man schneller, wenn man das ganze
Projekt in C nochmal neu schreibt. Zeitkritische Dinge kann man ja als
Assemblerteil übernehmen und anpassen. Aber der grundsätzliche
Projektaufbau wird auch in C nicht so aufwändig zu machen sein. Ich
würds so machen, anstatt da jetzt eine Menge Zeit in ein mit der heissen
Nadel gestricktes C-Assembler Konglomerat zu stecken.
Horst S. schrieb:> Nu habe ich gelesen, das> GCC-Assembler ist nicht so ganz identisch mit dem AVRStudio-Assembler.
Kann man mit ein paar Definitionen ändern:
z.B.
Horst S. schrieb:> Hi,>> folgende Problematik mit dem WinAVR:>> In einem (etwas) gewachsenen Controller-Projekt (in Assembler) möchte> ich erstmals einige Funktionen in C schreiben. Nu habe ich gelesen, das> GCC-Assembler ist nicht so ganz identisch mit dem AVRStudio-Assembler.> Die Assemblerfiles direkt im C-Projekt einzubinden, scheitert zumindest> erst einmal an den Präprozessordirektiven, ob Makros & Co funktionieren,> hab ich noch gar nicht ausprobiert.>> Frage also: Gibt es irgendeine (einfache!!!) Lösung (über Linker, Tools> oder sonstewas), das Pferd von hinten aufzuzäumen und [...]
Du verwendest den Atmel-Assembler? AFAIK erzeugt der keine linkfähigen
Objekte, sondern ammes ist in einer riesigen Moster-Datei (oder wird
über includes zu einer solchen zusammengefügt, was auf das gleiche
hinausläuft) und das wird dann zu einem nicht-linkfähigen, ausführbaren
Format zusammengebacken.
Das Problem ist also zunächst nicht, C von Assembler aus zu verwenden
ober umgekehrt, sondern dass der Atmel-Assembler keine ABI-kompatiblen
(d.h. elf32-avr, desser RELOCs, Symboltabbellen, etc.) erzeugen kann.
Wenn du es geschafft hast, linkbare Objekte aus deinem Assembler zu
erzeugen, ist der Rest eigentlich recht simpel.
D.h. wenn du in diese Ricktung weitermachen willst, dann musst du
zunächst das Atmel-Zeugs auf GNU-Assembler portieren.
Hmm,
das klingt ja gar nicht mal erbaulich. Wäre ja auch zu schön gewesen,
wenn's mal einfach geht.
Wenn ich das alles so richtig überblicke, ist der Assemblercode
eigentlich nicht das gravierende Problem (wenn ich über Push/Pop sauber
die Register auf den Stack geladen habe, das habe ich, weil ich
substanziell eigentlich kein Hauptprogramm verwende und das
pushen/poppen der Register im Interrupt ist Pflicht). Es scheint eher
ein Heap-Verwaltungsproblem. Das führt mich gleich zu weiteren Fragen:
Ich habe im Assembler eine 2-stufige serielle Kommunikation aufgebaut,
die im wesentlichen Datenblöcke lesend und schreibend (UART oder TWI)
überträgt:
1. Stufe: Master fragt über eine feste Adresse (natürlich RAM_Start=
0x100) eine Tabelle ab, die über ID/Name, Startadresse und Länge einen
Datenblock (lesend oder schreibend) im RAM beschreibt.
2. Stufe: Master schreibt oder liest anhand der Tabelleninformationen
den gewünschten Datenblock.
In der Praxis fummelt sich der Communicator (Controller mit
PC-Schnittstelle) beim Start über den TWI-Bus eine Datenblocktabelle der
angeschlossenen Slaves (inklusive seiner selbst) zusammen. Diese holt
sich der PC, lädt die entsprechenden Objekte anhand der
Datenblocktabelle und fängt danach an, über den Communicator mit den
TWI-Slaves zu kommunizieren.
Die Tabelle liegt also im RAM, weil sie nicht nur die Datenblöcke des
Controllers selbst beschreibt, sondern auch ggf. die Datenblöcke
weiterer Controller, die erst abgefragt werden (das System ist steckbar
- Plug and Pray).
Nun zum Heap: Um in C überhaupt die Tabelle zu definieren, müsste ich
wohl zunächst eine Struktur für einen Tabelleneintrag definieren und
davon eine Feldvariable (Variablenfeld?) bauen.
Frage: Wie pinne ich mit C denn im RAM die Tabelle auf eine feste
Adresse (muss ja jetzt nicht unbedingt 0x100, sollte aber schon in allen
Controllern die gleiche sein)? Oder muss ich diese eine spezielle
Adresse per Code umbiegen auf den Zeiger der Tabellenvariable?
Nächste Frage: Die Variablen in den Datenblöcken habe ich zur Zeit in
Assembler im DSEG hintereinander definiert, so werden sie auch
physikalisch angeordnet. Wenn ich jetzt in C einen Datenblock als
Struktur definiere, kann ich dann davon ausgehen, dass auch hier die
einzelnen Bytes der Elemente physikalisch hintereinanderliegen oder
laufe ich hier ins Messer (Alignment/ Optimizer/ andere Fallstricke)?
Last but not least: Wenn ich dann ggf. über Rest-Assembler auf die
Strukturen auf dem Heap zugreife, verwende ich dann die Strukturadresse
+ errechneten Offset? Oder kann ich per Makro direkt die Adresse eines
Strukturelementes herausfinden?
In diesem Sinne, über weiteren Input würde ich mich freuen.
Gruß Horst
Grundsätzlich ist die combination von assembler und c/c++/Fortan etc.
unproblematisch, solange man gcc wissen läst, was man tut. Der bei gcc
enthaltene GNU Assembler (GAS) kann aus assembler Object-files machen.
Mit den direktiven .extern und .global können symbole exportiert und
importiert werden. Mehr zu GAS gibt's hier:
http://tigcc.ticalc.org/doc/gnuasm.html#SEC67
Ich würde die Initialisierung und den main loop in asm in funktionen
packen, und dann diese mit global exportieren. Die funktionen dann in
der c main aufrufen. Die c main aus asm aufzurufen ist sehr kompliziert,
deshalb besser umgekehrt, damit alles richtig initialisiert ist.
Horst S. schrieb:> kann ich dann davon ausgehen, dass auch hier die einzelnen Bytes der> Elemente physikalisch hintereinanderliegen oder laufe ich hier ins> Messer
bei einem c struct gibt es padding wegen dem alinement. Mit gcc kann man
das per attribut packed ausschalten.
Horst S. schrieb:> Wie pinne ich mit C denn im RAM die Tabelle auf eine feste Adresse
garnicht. Man kann das jedoch in einem linker file angeben. Warum nicht
einfach ein array/pointer in c als extern declarieren, und in asm die
.extern direktive nutzen?
Die einzige sinnvolle Lösung dir mir einfällt ist dein Assemblercode an
GCC-Assembler anzupassen, was nicht so schwierig sein dürfte. Ein GCC
C-Projekt aufzusetzen mit Assembler zu kombinieren. Deinen Assemblercode
ins .s File zu packen deine C-Funktion ins .c und schon sollte es
laufen. Dazu die Application Note von Atmel zum Thema "Mixing C and
Assembly" für 8-Bit Controller lesen.
Wenn Du keine Register als globale Variablen nimmst, als immer schön
brav push/lds/sts/pop machst, hast Du die erste Hürde geschafft.
Dann mußt Du noch sämtliche globalen Variablen nicht an feste Adressen
legen, sondern vom Linker plazieren lassen.
Im AVR-GCC Example Ordner findest Du ein Beispiel, für Assembler + C.
Ich bin allerdings auch der Meinung: Besser komplett in C neu schreiben.
Wenn man den Programmablaufplan des Assembler-Programms noch im Kopf
hat, sollte das schnell gehen.
Nach reiflicher Überlegung (jau, ich hab mal wirklich nachgedacht) habe
ich beschlossen, das Projekt scheibchenweise auf C umzustellen.
Einige Dinge laufen schon im neuen gcc-Projekt. Zumindest versteht das
Controllerprogramm schon meine Initialanforderung zur Übertragung der
Datenblocktabelle.
Zwei Augenburner habe ich allerdings nicht so richtig hinbekommen.
1. Burner:
Aus meinem AVR-Assembler-Makro...
1
//Used to set the X-, Y- or Z-Register to a Ram address
2
// Example: SetRamPointer X, MyRamAddress
3
.MACRO SetRamPointer
4
ldi @0H, High(@1)
5
ldi @0L, Low(@1)
6
.ENDMACRO
...wurde nach einigem Fummeln GAS-like:
1
.macro SetRamPointer register, address
2
.ifeqs "\register" , "X"
3
ldi XH, hi8(\address)
4
ldi XL, lo8(\address)
5
.endif
6
7
.ifeqs "\register" , "Y"
8
ldi YH, hi8(\address)
9
ldi YL, lo8(\address)
10
.endif
11
12
.ifeqs "\register" , "Z"
13
ldi ZH, hi8(\address)
14
ldi ZL, lo8(\address)
15
.endif
16
.endm
irgendwie hab ich's beim besten Willen nicht hinbekommen, H und L an den
\register-Parameter zu hängen. Ist ja nur 'nen Makro, macht also von der
Performance nix. Ich bekomme aber immer Augenkrebs bei solchen Lösungen.
Geht das also einfacher?
2. Burner, der mich stört:
in C definiert...
zu 1:
ich hab' keinen GNU assembler für AVR und kann's deswegen nicht
ausprobieren, weiß aber, daß sich gas auch auf anderen Plattformen gern
mal weigert, Registernamen oder Konstanten und Macro-Parameter einfach
so aneinanderzuhängen.
Ich habe mir dann immer mit dem Trick beholfen "\()" (das löst zu
"nichts" auf) ans Ende des Parameters zu kleben, damit war's meist zum
Laufen zu bringen.
Anno Studio 4.16 gab’s zu den C-Bibliotheken eine Liste mit FAQs. Da
wurde, zwar aus Gründen der Einbindung von Assembler in C, die
Registerbelegung der Umgebung erläutert.
Das sollte eigentlich helfen.
Zum Abschluss,
Danke an alle, hat geholfen, fertig, getestet, geht.
Grundsätzlich festzuhalten also: Umstellung von AVR-Assembler auf GAS im
Rahmen eines GCC-Projektes funktioniert. Voraussetzungen hierfür
sicherlich ein Assemblerprojekt, in dem die Register "sauber" über
push/pop von allen anderen Funktionen entkoppelt sind.
Randbemerkungen noch:
- Substanziell: Makros haben eine andere Syntax, Da hilft nur
"durchbeißen". (Das o.g. Makro z.B. ließ sich unverständlicherweise,
trotzdem ich den "\()"-Tipp auch in den AVR-Freaks-Foren wiedergefunden
habe, nicht weiter optimieren, schade, ich hab's soweit vergraben, dass
ich es hoffentlich nie wieder sehen muss).
- Auch essentiell: Alle Zugriffe auf den unteren IO-Bereich (also alles
was mit /in/out/ sbi/sbic,...) IMMER (egal, ob der Compiler meckert, das
hat er bei mir nicht immer getan) mit _SFR_IO_ADDR() umgeben.
- Das Problem mit den Zugriffen auf die Strukturelemente in GAS habe ich
soweit für mich gelöst, dass jetzt sowohl Assembler als auch C-Code die
gleiche Header-Datei nutzen, in der zumindest die typedefs (für C) und
die zugehörigen Offsets (für Assembler) über ein #ifdef getrennt...
1
#ifndef __ASSEMBLER__
2
typedefstruct
3
{
4
uint8_tState;
5
uint8_tRawData[8192];
6
}__attribute__((packed))DataReadBlock;
7
#else
8
//READ MEMORY OFFSETS
9
.equOFFS_State,0;
10
.equOFFS_RAWDATA,1;
11
.equOFFS_RAWDATA_END,OFFS_RAWDATA+8192;
12
#endif
...untereinander stehen. So vergesse ich zumindest nicht, die Offsets
anzupassen, wenn ich die Struktur erweitere.
- Einige der o.g. Links sind sicherlich hilfreich, allerdings auch kein
Allheilmittel. Ohne beide Sprachen (C UND ASSEMBLER) zumindest zu
verstehen, hätte ich massiv Probleme gehabt.
Keine Woche um, und schon ein Stückchen weiter...
Danke nochmal
Gruß Horst