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?
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.
Ist schon klar, aber da kannst du auch etwas über die Funktionen lesen, wie das im Studio so arbeitet.
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
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
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.
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
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 "
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?
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.
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.
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
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.
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.)
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.
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.
Arduino F. schrieb: > braucht es nur Deklarationen auf deutsch wären das Erklärungen, aber gut [ein Polyglot]
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
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!
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
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
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
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.
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
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.
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.
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.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.