Forum: Compiler & IDEs C in Assembler einbetten (also andersrum!)


von Horst S. (Gast)


Lesenswert?

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

von Peter II (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Ralf G. (ralg)


Lesenswert?

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.
1
r0    = 0
2
r16    = 16
3
r17    = 17
4
r18    = 18
5
r19    = 19
6
r20    = 20
7
r21    = 21
8
r22    = 22
9
r23    = 23
10
r24    = 24
11
r25    = 25
12
r30    = 30
13
r31    = 31
14
sreg  = _SFR_IO_ADDR(SREG)
15
16
17
18
ucsra  = _SFR_IO_ADDR(UCSRA)
19
ucsrb  = _SFR_IO_ADDR(UCSRB)
20
ucsrc  = _SFR_IO_ADDR(UCSRC)
21
22
udr    = _SFR_IO_ADDR(UDR)
23
24
// usw.
dann kannst du programmieren, wie gewohnt.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Horst S. (Gast)


Lesenswert?

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

von Daniel A. (daniel-a)


Lesenswert?

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?

von Thomas H. (Firma: CIA) (apostel13)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

: Bearbeitet durch User
von Horst S. (Gast)


Lesenswert?

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...
1
 
2
typedef struct 
3
{
4
  uint8_t TWIAddress; 
5
  char Name[8];
6
  uint8_t ID;
7
  uint16_t ReadBufferAddress;
8
  uint16_t ReadBufferLength;
9
  uint16_t WriteBufferAddress;
10
  uint16_t WriteBufferLength;
11
  uint16_t Comm_ReadBufferAddress;
12
  uint16_t Comm_WriteBufferAddress;
13
  uint8_t LockByte;
14
}__attribute__ ((packed)) PeripherieTableEntry;
15
16
volatile PeripherieTableEntry PeripherieTable = {255, "Jojo1.0 ", 255, &DataRead, sizeof(DataRead), &DataWrite, sizeof(DataWrite), &DataRead, &DataWrite, 0};

...muss ich im GAS Assembler jetzt per Hand die Offsets auf die 
Strukturelemente selbst definieren?....
1
//PERIPHERIE TABLE OFFSETS
2
//Constants describing a PeripherieTable entry
3
.equ PT_OFFS_TwiId, 0;           // TWI slave ID
4
.equ PT_OFFS_Name, 1;           // textual name
5
.equ PT_OFFS_PeriId, 9;          // Unique PeripherieID for hardware: 0..127: Actor, 128..255: Sensor
6
.equ PT_OFFS_SL_ReadMem_Addr, 10;      // ReadMemory address in Slave 
7
.equ PT_OFFS_SL_ReadMem_Length, 12;    // ReadMemory length (in Bytes) in Slave 
8
.equ PT_OFFS_SL_WriteMem_Addr, 14;    // WriteMemory address in Slave 
9
.equ PT_OFFS_SL_WriteMem_Length, 16;    // WriteMemory length (in Bytes) in Slave 
10
.equ PT_OFFS_ReadMem_Addr, 18;      // Buffer for ReadMemory address in Communicator
11
.equ PT_OFFS_WriteMem_Addr, 20;      // Buffer for WriteMemory address in Communicator
12
.equ PT_OFFS_LOCK, 22;          // Lock byte
13
14
15
16
17
//Sample Access
18
SampleAccess:
19
   SetRamPointer X, PeripherieTable + PT_OFFS_PeriId // 
20
   ld d0, X+  // r16 aliases as d0
21
   ....

...oder gibt's zur Auflösung der Elemente ein Makro im GAS?




Gruß Horst

von Markus F. (mfro)


Lesenswert?

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.

von Amateur (Gast)


Lesenswert?

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.

von Horst S. (Gast)


Lesenswert?

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
typedef struct
3
{
4
  uint8_t State; 
5
  uint8_t RawData[8192]; 
6
}__attribute__ ((packed)) DataReadBlock;
7
#else
8
//READ MEMORY OFFSETS
9
  .equ OFFS_State, 0;
10
  .equ OFFS_RAWDATA, 1;
11
  .equ OFFS_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

von Peter D. (peda)


Lesenswert?

Hier mal ein Beispiel für C + Assembler:

Beitrag "AVR: Fast-PWM (BAM) 12 Bit für 8 Kanäle"

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.