Forum: Mikrocontroller und Digitale Elektronik C++ in C-Code in Microchip Studio einbinden


von Wulf D. (holler)


Lesenswert?

Ich habe ein AVR-uC Projekt in Microchip Studio 7 in C-Code umgesetzt.
Jetzt möchte ich für die Einbindung eines komplexeren Chips eine Library 
aus dem Arduino-Lager verwenden. Das ist C++-Code.
Klar, die Arduino-typischen Lib-Funktionen wie digitalWrite muss ich 
nachbilden, aber das ist nicht der Punkt.

Es geht darum, wie ich mein für Standard-C aufgesetztes Projekt den 
C++-Code unterschieben kann?

Habe z.B. versucht, der Klassendeklaration im C++-Headerfile ein extern 
"C" class foo; voranzustellen, aber da meckert das Studio sowohl am "C" 
als auch am class-Schlüsselwort herum.

Bevor ich den ganzen C++-Code nach C auflöse: da gibt es doch sicher 
elegantere Lösungen?
Hat jemand das schon erfolgreich umgesetzt und kann Tipps geben?

von Frank O. (frank_o)


Lesenswert?


von Wulf D. (holler)


Lesenswert?

Frank O. schrieb:
> https://gallery.microchip.com/packages/324cac6d-ff67-4e2e-8fc9-7a587b2d6045/

Nee, das soll kein Arduino-Projekt werden. Möchte nur C++-Code in ein 
C-Projekt einbinden.

von Frank O. (frank_o)


Lesenswert?

Ist schon klar, aber da kannst du auch etwas über die Funktionen lesen, 
wie das im Studio so arbeitet.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Wulf D. schrieb:
> Frank O. schrieb:
>> https://gallery.microchip.com/packages/324cac6d-ff67-4e2e-8fc9-7a587b2d6045/
>
> Nee, das soll kein Arduino-Projekt werden. Möchte nur C++-Code in ein
> C-Projekt einbinden.

Du musst viele Wrapper bauen, um C++ Dinge in C nutzen zu können.
Andersrum ist meist einfacher.
Um C in C++ einzubinden braucht es nur Deklarationen

: Bearbeitet durch User
von Oliver S. (oliverso)


Lesenswert?

Wulf D. schrieb:
> Bevor ich den ganzen C++-Code nach C auflöse: da gibt es doch sicher
> elegantere Lösungen?
> Hat jemand das schon erfolgreich umgesetzt und kann Tipps geben?

Mach halt ein vollständiges C++-Projekt daraus.

Oliver

von Wulf D. (holler)


Lesenswert?

Arduino F. schrieb:
> Du musst viele Wrapper bauen, um C++ Dinge in C nutzen zu können.
> Andersrum ist meist einfacher.
> Um C in C++ einzubinden braucht es nur Deklarationen

Meine Hoffnung war, dass ich eine statische Instanz der Klasse erzeuge 
und dann um einzelne Wrapper für jedes Member drum rum komme.
Wenn das nicht geht, lohnt der Aufwand kaum, die Klasse hat Unmengen an 
Memberfunktionen und Properties.

Umgekehrt, also C-Code in C++, geht wirklich ohne Aufwand, das kenne 
ich.


Oliver S. schrieb im Beitrag #779140
> Mach halt ein vollständiges C++-Projekt daraus.

Das wäre auch eine Möglichkeit, aber noch aufwändiger als die C++ Klasse 
zu zerlegen. Habe in das Projekt bereits eine Display C-Lib und anderes 
integriert.
Wahrscheinlich müsste ich das Projekt im Microchip Studio auch neu 
aufsetzen, momentan meckert der alles an was nicht plain C ist.

von Oliver S. (oliverso)


Lesenswert?

Wulf D. schrieb:
> Das wäre auch eine Möglichkeit, aber noch aufwändiger als die C++ Klasse
> zu zerlegen.

Das bezweifele ich mal. Es kommt natürlich auf die Klasse an, aber 
üblicherweise zieht die eine Rattenschwanz an anderen hinterher.

> Habe in das Projekt bereits eine Display C-Lib und anderes
> integriert.

Ein paar „extern C“ dazu, und schon passt, wenn du den Code nicht auch 
einfach als C++ übersetzt.

Oliver

von Hans-Georg L. (h-g-l)


Lesenswert?

1.) Benutze für alles ein c++ Compiler, der kennt auch C code und macht 
das richtig für .c files.

2.) Alle C Header deklarieren mit:
#ifdef __cplusplus
extern "C" {
#endif

...code

#ifdef __cplusplus
}
#endif

3.) Für alle C++ Klassen musst du Wrapper bauen damit du die von C aus 
aufrufen kannst "

von Wulf D. (holler)


Lesenswert?

Ok Danke, probiere ich morgen Abend einmal.

von Hans-Georg L. (h-g-l)


Lesenswert?

Nachtrag: alle Wrapper mit extern c deklarieren

von Wulf D. (holler)


Lesenswert?

Also beide Vorschläge, Olivers und Hans-Georgs, probiere ich morgen aus.

Wenn ich nur für die Klasse einen Wrapper definieren muss, ist das 
schnell erledigt. Oder muss für jede Member-Funktion etwas definiert 
werden?

von Hans-Georg L. (h-g-l)


Lesenswert?

Wulf D. schrieb:
> Also beide Vorschläge, Olivers und Hans-Georgs, probiere ich morgen aus.
>
> Wenn ich nur für die Klasse einen Wrapper definieren muss, ist das
> schnell erledigt. Oder muss für jede Member-Funktion etwas definiert
> werden?

Natürlich für jede Member Funktion .. C kennt keine Klassen.

von Frank O. (frank_o)


Lesenswert?

Das ist doch immer so. Wenn man rechts anfängt und dann nach links geht 
und von da weiter machen will, ist es nicht immer einfach die Mitte zu 
treffen.

Ich würde erstmal schauen, ob nicht doch eine Lib dafür in C zu finden 
ist.

von Wulf D. (holler)


Lesenswert?

Frank O. schrieb:
> Ich würde erstmal schauen, ob nicht doch eine Lib dafür in C zu finden
> ist.
Es geht um einen Treiber für einen TMC2209 Steppermotor. Hatte auch 
einen gut dokumentierten C-Code gefunden:
https://www.programming-electronics-diy.xyz/2023/12/library-for-tmc2209-driver-avr.html

Leider kommt der mit einem aufwändigen Timer-Interrupt von 40us(!) 
daher, noch nicht genau geschaut, warum der so hochfrequent benötigt 
wird. Vermutlich um die Steps einzeln per Timer-Interrupt anzustoßen: 
nicht sehr überzeugend, für so etwas nutzt man die Hardware-Resourcen 
des Controllers.

Aber mal schauen ob ich anderen C-Code finde oder auf Basis des im 
Thread angesprochenen C++ Lib etwas baue. Die umgeht die 
Schritterzeugung komplett und nutzt den im TMC eingebauten (Behelfs-) 
Generator, aber da könnte man ja einen Zähler des AVR einbringen.
Der Code ist deutlich übersichtlicher, nur die Templates am Ende finde 
ich etwas krass: aber vielleicht macht man das so heute.
https://github.com/janelia-arduino/TMC2209/blob/main/src/TMC2209.h#L610

von Harald K. (kirnbichler)


Lesenswert?

Wulf D. schrieb:
> Es geht um einen Treiber für einen TMC2209 Steppermotor.

Was macht denn der C++-Code so besonderes?

Könnte es vielleicht einfacher sein, das, was da passiert, von seiner 
C++-Hülle zu befreien, d.h. nach C zu portieren?

Ich wüsste nicht, warum man ausgerechnet beim zeitkritischen Ansteuern 
eines Schrittmotors mit Objektorientierung arbeiten muss.

Nichts gegen Objektorientierung und Nutzung moderner 
Programmiersprachenfeatures, aber dort, wo sie angebracht sind. Und auf 
Treiberebene, wo es auf zeitkritische Dinge angeht, möchte man 
vielleicht nicht mit einer vtable, virtuellen Konstruktoren etc. pp. 
herummachen.

von Frank O. (frank_o)


Lesenswert?

Wulf D. schrieb:
> Leider kommt der mit einem aufwändigen Timer-Interrupt von 40us(!)
1
1.2.1 UART Interface
2
The single wire interface allows unidirectional operation (for parameter setting only), or bi-directional
3
operation for full control and diagnostics. It can be driven by any standard microcontroller UART or
4
even by bit banging in software. Baud rates from 9600 Baud to 500k Baud or even higher (when using
5
an external clock) may be used. No baud rate configuration is required, as the TMC2209 automatically
6
adapts to the masters’ baud rate. The frame format is identical to the intelligent TRINAMIC controller &
7
driver ICs TMC5130, TMC5160 and TMC5072. A CRC checksum allows data transmission over longer
8
distance. For fixed initialization sequences, store the data including CRC into the µC, thus consuming
9
only a few 100 bytes of code for a full initialization. CRC may be ignored during read access, if not
10
desired. This makes CRC use an optional feature! The IC supports four address settings to access up to
11
four ICs on a single bus. Even more drivers can be programmed in parallel by tying together all interface
12
pins, in case no read access is required. An optional addressing can be provided by analog multiplexers,
13
like 74HC4066.
14
From a software point of view the TMC2209 is a peripheral with a number of control and status registers.
15
Most of them can either be written only or are read only. Some of the registers allow both, read and
16
write access. In case read-modify-write access is desired for a write only register, realize a shadow
17
register in master software.

Das hört sich für mich nicht so an.
Aber ich hatte noch nicht den Bedarf irgendwas mit Steppern zu machen. 
(Obwoh ich hier auch so was mal bestellt hatte.)

von Wulf D. (holler)


Lesenswert?

Harald K. schrieb:
> Was macht denn der C++-Code so besonderes?

Er ist halt übersichtlich und fasst die zahlreichen Register des Chips 
in lesbaren structs zusammen.

> Könnte es vielleicht einfacher sein, das, was da passiert, von seiner
> C++-Hülle zu befreien, d.h. nach C zu portieren?

Ist auf jeden Fall eine Option.

> Ich wüsste nicht, warum man ausgerechnet beim zeitkritischen Ansteuern
> eines Schrittmotors mit Objektorientierung arbeiten muss.

Muss man sicher nicht, sehe ich genau so. Schadet aber auch nicht. Nur 
mit Templates und Iteratoren bin ich nicht auf dem Laufenden, das gabs 
zu der Zeit noch nicht, als ich mit Programmcodes beruflich zu tun 
hatte. Sollte ich mich für den Code entscheiden, würde ich die beiden 
C++-Features auch eher rausnehmen.


Frank O. schrieb:
> Wulf D. schrieb:
>> Leider kommt der mit einem aufwändigen Timer-Interrupt von 40us(!)
> ...
>
> Das hört sich für mich nicht so an.
> Aber ich hatte noch nicht den Bedarf irgendwas mit Steppern zu machen.
> (Obwoh ich hier auch so was mal bestellt hatte.)
Doch, ist in dem Fall so:
1
micros.h and micros.c: keeps track of microseconds with 40 microseconds resolution. Included by stepperCon.h. Default timer used is Timer2. Here F_CPU is defined as 16MHz. Consider modifying this if your microcontroller uses different frequency.

Stecke in den Steppern auch nicht so drin, habe jetzt aber eine 
Anwendung die perfekt dafür geeignet ist. Mit dem modernen 
TMC2209-Treiber und einer passenden Lib wollte ich den tiefen Details 
auch gar nicht auf den Grund gehen.

Es gibt halt zahlreiche Optimiererungen bei der Ansteuerung 
(Strombegrenzung, Geräusch, Resonanzunterdrückung, Wärmereduzierung, 
Stillstand-Verhalten, Stall-Erkennung, ... ), mit der man sich 
beschäftigen könnte. Oder halt Treiber / SW-LIB überlassen und einfach 
nur ausprobieren wie es sich in der Anwendung verhält.

von Harald K. (kirnbichler)


Lesenswert?

Wulf D. schrieb:
> Er ist halt übersichtlich und fasst die zahlreichen Register des Chips
> in lesbaren structs zusammen.

Dafür braucht man kein C++, das geht in C ebenfalls.

Wulf D. schrieb:
> Schadet aber auch nicht.

Kann aber, sobald z.B. eine Funktion aufgerufen wird, die lokal eine 
Objektinstanz anlegt, bei der dann natürlich erst mal der Konstruktor 
aufgerufen wird ... und erst recht, wenn irgendwo ein new verwendet 
wird.
Da ist dann der Überblick über den Preis der Operationen nicht mehr 
leicht zu behalten.

von Bursch (Gast)


Lesenswert?

Arduino F. schrieb:
> braucht es nur Deklarationen

auf deutsch wären das Erklärungen, aber gut

[ein Polyglot]

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Bursch schrieb:
> Erklärungen
Auch.

Nicht nur:
> Erklärung, Kundmachung, Offenbarung
Es sind "Bekanntmachungen", welche den C++ "offenbaren", dass diese und 
jene Funktion, erstens existieren und zweitens mit einem C Compiler 
übersetzt wurden. Ohne diese Offenbarung findet der Linker sie nicht.
In C++ sind diese Funktionen, nach der "Erklärung", nutzbar.

Siehe dazu:
Hans-Georg L. schrieb:
> extern "C"

Siehe dazu z.B. dieses: 
https://www.emmtrix.com/wiki/Demystifying_C%2B%2B_-_Name_Mangling

: Bearbeitet durch User
von Bursch (Gast)


Lesenswert?

Arduino F. schrieb:
> Es sind "Bekanntmachungen",

irgendwie und sowieso ack

von Frank O. (frank_o)


Lesenswert?

Wulf D. schrieb:
> Doch, ist in dem Fall so:micros.h and micros.c: keeps track of
> microseconds with 40 microseconds resolution. Included by stepperCon.h.
> Default timer used is Timer2. Here F_CPU is defined as 16MHz. Consider
> modifying this if your microcontroller uses different frequency.

Ok!

von Falk S. (falk_s831)


Lesenswert?

Lohnt das wirklich den Aufwand, die ganzen C++-Symbole auf C-Exporte 
umzustellen? Zum Einen brauchste so oder so 'nen C++-Compiler, damit 
überhaupt die C++-Sprachfeatures richtig geparst und übersetzt werden 
(C11 ist anders als C++11!) und zum Anderen gewinnst du nicht wirklich 
was damit. Bei einer DLL würd ich's ja noch halbwegs einsehen, für die 
Schnittstelle die Funktionen als C-Symbole zu exportieren - aber nur für 
eine statische Lib (oder eben ne Handvoll an .cpps) bringt das nicht 
viel.

Aber wenn's unbedingt sein muss, kannste dir ja nen C-Proxy basteln, 
quasi nach dem Muster überführt:
1
class A 
2
{ 
3
public:
4
  A(){} 
5
  ~A(){} 
6
  void foo() {} 
7
}

zu
1
extern "C" {
2
3
typedef void* Handle_A;
4
5
Handle A_create();  // konstruktor
6
void A_destroy(Handle obj); // destruktor
7
void A_foo(Handle obj); // members
8
9
} // extern "C"

Oder nimmst SWIG o.ä. Lohnt aber den Aufwand eher selten.

: Bearbeitet durch User
von Wulf D. (holler)


Lesenswert?

Ja, ich denke auch dass die Integration der C++-Lib nicht der richtige 
Weg ist. Die hat viel zu viele Members.
Und die u.g. C-Lib hat zu viele externe Abhängigkeiten.

Weiß nicht, warum ich das nicht schon zuvor fand: Analog Devices hat im 
Github selbst eine API eingestellt. Plain C, dokumentiert, 
Prozessor-unabhängig und ohne weitere Abhänigkeiten. Die schaue ich mir 
jetzt genauer an:
https://github.com/analogdevicesinc/TMC-API/tree/master

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Oliver S. schrieb:
> Mach halt ein vollständiges C++-Projekt daraus.

Dazu würde ich auch raten. Es ist absolut gängig C++ -Projekte mit 
(vielen) C-Bibliotheken zu verwenden, sowohl auf dem PC aus auch 
Embedded. Ich und viele andere machen das ständig. Umgekehrt hingegen 
ist unüblich und umständlich.

Harald K. schrieb:
> Und auf
> Treiberebene, wo es auf zeitkritische Dinge angeht, möchte man
> vielleicht nicht mit einer vtable, virtuellen Konstruktoren etc. pp.
> herummachen.

Virtuelle Konstruktoren gibt es nicht. vtables basieren auch nur auf 
Funktionszeigern. Wenn die Alternative ein "einzelner" Funktionszeiger 
oder ein switch-case ist, sind virtuelle Funktionen (vtables) praktisch 
genau so schnell. Besonders wenn man mehrere davon der selben Instanz 
hintereinander aufruft.

Wulf D. schrieb:
> Nur
> mit Templates und Iteratoren bin ich nicht auf dem Laufenden, das gabs
> zu der Zeit noch nicht, als ich mit Programmcodes beruflich zu tun
> hatte. Sollte ich mich für den Code entscheiden, würde ich die beiden
> C++-Features auch eher rausnehmen.

Beide Features funktionieren wunderbar auch auf Embedded-Systemen. 
templates können den Code sogar beschleunigen dank inlining (statt 
Funktionszeiger). Integer und Pointer sind auch Iteratoren und können 
problemlos genutzt werden. Wenn der jeweilige Container geeignet ist, 
sind es dessen Iteratoren auch, z.B. std::array, std::span oder auch 
boost::intrusive::slist.

Harald K. schrieb:
> sobald z.B. eine Funktion aufgerufen wird, die lokal eine
> Objektinstanz anlegt, bei der dann natürlich erst mal der Konstruktor
> aufgerufen wird ...

Muss kein Problem sein, wenn der Konstruktor simpel und "inline" ist. 
Ein Konstruktor ist auch nur eine Funktion. Irgendwo müssen die Dinge 
halt gemacht werden.

Ein kleines Beispiel: https://godbolt.org/z/P9M4hzW1s
Es werden mehrere Objekte mit Konstruktoren und templates sowie 
Iteratoren genutzt. Der generierte Code ist so gut wie optimal, mir 
fällt auf die Schnelle nichts auf was da noch besser sein könnte. Das 
Beispiel ist natürlich "künstlich" um sehen was der Compiler spezifisch 
aus den jeweiligen Sprachkonstrukten macht ohne "Drumherum".

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Niklas G. schrieb:
> Muss kein Problem sein, wenn der Konstruktor simpel und "inline" ist.

Wenn. Das ist das Schlüsselwort. Wenn aber der C++-Code ein "lib" mit 
"reinzieht", die der Programmierer des Codes nicht selbst geschrieben 
hat und mangels Erfahrung nicht einschätzen kann, und er sie nur von 
seinen Fingerübungen auf dem PC kennt, dann ist die sprichwörtliche 
Büchse oder Dose der Pandora offen.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Harald K. schrieb:
> Wenn aber der C++-Code ein "lib" mit
> "reinzieht", die der Programmierer des Codes nicht selbst geschrieben
> hat und mangels Erfahrung nicht einschätzen kann, und er sie nur von
> seinen Fingerübungen auf dem PC kennt, dann ist die sprichwörtliche
> Büchse oder Dose der Pandora offen.

Das ist in allen Sprachen so und hat nichts mit C++ zu tun. In C kann 
man auch riesige Bibliotheken "reinziehen". Gerade die in C ja so heiß 
geliebten Makros können bei der gerne mal undurchdringlichen 
Verschachtelung alle möglichen Dinge enthalten, wie z.B. einen Aufruf an 
"malloc()" oder "printf()" oder whatever.

: Bearbeitet durch User
von Frank O. (frank_o)


Lesenswert?

Wulf D. schrieb:
> Weiß nicht, warum ich das nicht schon zuvor fand:

Ein bisschen länger suchen ist dann doch manchmal die geringere Arbeit.
Ich habe mal was gebaut, dass es meiner Meinung nicht gab.
Da war ich gerade fertig, alles funktionierte und dann fand ich etwas, 
das mehr konnte, viel geiler aussah und billiger war, als alle meine 
Bauteile gekostet haben.
Deshalb immer erstmal ordenlich suchen.

von Harald K. (kirnbichler)


Lesenswert?

Niklas G. schrieb:
> Das ist in allen Sprachen so und hat nichts mit C++ zu tun. In C kann
> man auch riesige Bibliotheken "reinziehen".

Das ist in C erheblich aufwendiger als in den mittlerweilse so beliebten 
Sprachen wie z.B. Rust, wo ein Paketmanager gleich zum Sprachumfang 
gehört.

Stell' dich nicht so an, Du weißt, was ich meine.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Harald K. schrieb:
> wo ein Paketmanager gleich zum Sprachumfang gehört.

Mit CubeMX oder auch Frameworks wie Zephyr hat man auch in C in 
Nullkommanix eine gigantische Menge Code eingebunden. Der 
Rust-Paketmanager bindet auch nicht ungefragt Bibliotheken ein alleine 
durch Programmcode, man muss sie explizit hinzufügen. Ja, Abhängigkeiten 
werden automatisch aufgelöst, aber wie viele Embedded-Bibliotheken haben 
dort unnötig viele Abhängigkeiten? Dass man für ein 
Mikrocontroller-Projekt keine riesige Bibliothek für PC-GUIs einbindet 
ist klar.

Harald K. schrieb:
> Stell' dich nicht so an, Du weißt, was ich meine.

Ich weiß was du meinst und widerspreche dir.

von Wulf D. (holler)


Lesenswert?

Danke erstmal an die, von denen präzise Hinweise zum Einbinden von 
C++-Libs kamen. Ich habe es nicht umgesetzt, mir schien der Aufwand zu 
hoch.

Vielleicht sollte ich das nächste Embedded-Projekt in C++ aufsetzen. 
Habe ich erst einmal gemacht, da von vorn herein klar war, dass 
entsprechende Libs einzubinden waren.

Ich habe die im Thread genannte Schrittmotor C-Lib von Analog Devices 
eingebaut: da sieht man gleich dass das eine Arbeit von Profis ist. 
Schnörkellos und an den richtigen Stellen dokumentiert und vor allem mit 
nur minimalen Abhängigkeiten. In der "Grundausstattung" sind nur zwei 
Callbacks selbst zu implementieren. Für etwas mehr Funktionalität noch 
in einen 1ms-Timer-Interrupt einzuhängen.
So macht Integration Spaß, funktionierte fast auf Anhieb.

von Frank O. (frank_o)


Lesenswert?

Wulf D. schrieb:
> Ich habe die im Thread genannte Schrittmotor C-Lib von Analog Devices
> eingebaut:

Schön für deine Rückmeldung!
Es ist immer besser erstmal zu schauen, ob es nicht schon etwas gibt.
So schön auch Arduino ist, wenn du da was anderswo verwenden willst, 
musst du das halbe Arduino mitnehmen.

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.