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?
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?
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.
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.
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
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.
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.)
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.
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.
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.
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 im Beitrag #7791487:
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.
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.
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
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.