Inspiriert von dieser Diskussion:
Beitrag "C++ auf einem MC, wie geht das?" habe ich mich dran
gemacht und ein paar Klassen mit Beispielanwendung geschrieben. (Oups -
Dateiname wurde beim Upload verändert)
Bin soweit mit meinem Fortschritt ganz zufrieden.
Nur aktuell klemmt es etwas an der ADC-Geschichte und ich weiß nicht, ob
es ein Verständnisproblem, ein HW-Problem oder was es ist.
Die Testplatine ist eine selbstgemachte für einen mega16 in DIL Format
mit Stecksockeln und Pinbrücken. Über ein OPV-Modul habe ich einen
Kraftsensor angebunden, den ich per ADC auslesen kann. Das funktioniert
soweit auch.
Über Steckbrücken kann ich zusätzlich noch einen Taster ins Spiel
bringen.
Der Taster ist ein Pullup, d.h. active-high.
Wie man im Testprogramm sehen kann, habe ich 4 ADC-Eingänge aktiviert
und lese die zyklisch aus. Ohne Steckbrücke zum Taster habe ich auf den
4 Kanälen Werte zwischen 180 und 190. Stecke ich den Taster auf Kanal 3
oder 4 dann geht der "Dreckeffekt" auf 0 zurück. Seltsamerweise nicht
nur für den Kanal, wo der Taster dran hängt.
Ähnlich sieht es im aktiven Zustand aus. Der Sensor wird nur auf seinem
Kanal angezeigt (wie erwartet). Hänge ich den Taster dran und drücke den
Taster, so werden mehrere Kanäle mit 1024 angezeigt.
Kann das ein Übersprechen von der Platine sein, oder habe ich einen
gravierenden Fehler in der Software?
Die Klassen sind bislang nur für den m16 ausformuliert.
Ich würde da gerne noch weiter machen, aber erst, wenn ich die Sache
auch stabil bekomme.
>Seltsamerweise nicht nur für den Kanal, wo der Taster dran hängt.
Das ist nicht seltsam. Alle ADC Eingänge führen über einen Multiplexer
auf einen einzigen S+H Kondensator. Wenn der durch einen Eingang aktiv
entladen wird, wirkt sich dein "Dreck" von den anderen Eingängen weniger
aus.
Der ADC liefert nur dann sinnvolle Werte, wenn deine Signalquellen
maximal 10k Ohm Ausgangswiderstand haben. Bei weniger ist Übersprechen
zwischen den Kanälen genau so normal, wie falsche Messergebnisse.
Wenn du mit deiner Library-Sammlung so weiter machst, wirst du sehr bald
an die Grenzen des Flash Speichers kommen. Vor allem bei den kleineren
ATtinys. Da kannst du gleich Arduino nehmen.
Als Lernübung ist es allerdings Ok.
> Der ADC liefert nur dann sinnvolle Werte, wenn deine Signalquellen> maximal 10k Ohm Ausgangswiderstand haben. Bei weniger ist Übersprechen> zwischen den Kanälen genau so normal, wie falsche Messergebnisse.
OK, dann leuchtet mir zumindest ein, warum die "Dreck"-Messungen auf 0
gehen, wenn ich den Taster (T2) unbetätigt anschließe.
Wenn ich den allerdings drücke, dann habe ich den vollen Pegel auch auf
mehreren Eingängen.
Den Zusammenhang verstehe ich nicht.
> Wenn du mit deiner Library-Sammlung so weiter machst, wirst du sehr bald> an die Grenzen des Flash Speichers kommen. Vor allem bei den kleineren> ATtinys. Da kannst du gleich Arduino nehmen.
Naja - für Tinys ist das auch nicht gedacht. Bei denen habe ich auch in
Plain-C schon zu kämpfen, alles unter zu bringen. Nee, das ist eher für
die größeren AVRs gedacht. ZUdem auch als Übung, wie weit ich mit der
Abstraktion komme und wie weit die Geschichte für mich Sinn ergibt.
Ich gehe ähnlich vor, die Peter Dannegger, der erst überlegt, wie er den
Code verwenden will, bevor er ihn produziert ;)
Derzeit bin ich bei knapp 30% beim m16 - also (für mich) noch im grünen
Bereich :)
Ein paar Nachteile des Konzeptes habe ich schon identifiziert. Denen
will ich in einem Parallelprojekt mal auf den Grund gehen. Vor dem
Optimieren sollte es erstmal zuverlässig laufen.
Mit den virtuellen Methoden etc.. würd ich aufpassen, das braucht viel
Platz und hat auch Laufzeit-Overhead. Virtuelle Methoden bieten sich an,
wenn bestimmte Instanzen zur Laufzeit austauschbar sein müssen.
Wenn zur Compile-Zeit eh alles feststeht wirds mit templates kompakter
und schneller. Der Compiler hat zwar mehr zu tun, aber bei heutigen
Rechnern und durchschnittlichen µC-Projekten ist das kein Problem.
> dann habe ich den vollen Pegel auch auf mehreren Eingängen.
Weil du damit den S+H Kondensator auflädst und an den anderen offenen
Eingängen hängt nichts, was ihn wieder entlädt.
Schau Dir das mal an:
https://www.gammon.com.au/images/Arduino/ADC_internals.png
Das:
Reinhard M. schrieb:> Vor dem> Optimieren sollte es erstmal zuverlässig laufen.
stimmt mehr als das
rmu schrieb:> Mit den virtuellen Methoden etc.. würd ich aufpassen, das braucht viel> Platz und hat auch Laufzeit-Overhead.
also erst weitermachen und wenn du dann Platz oder Laufzeit Probleme
bekommst, kannst du immer noch schauen.
Eiegntlich bin ich ein großer Freund von C und habe C++ für Controller
immer vermieden.
Aber spätestens wenn man eine LIB für ein Modul hat, und mehrere dieser
Module an einem Controller anschliessen möchte, wünscht man sich
Klassen.
Klar kann man jeder Funktion eine Structur mitgeben. Aber allein die
Namenskonvention für Funktionen vereinfacht sich rapide. Oder wenn man
Module austauscht gegen ähnliche Module, dann hat C++ deutlich die Nase
vorn.
Deswegen meine Frage: Wie ist denn das Verhalten von C++ zu C in Bezug
auf Programmgröße und Ausführgeschwindigkeit?
>> dann habe ich den vollen Pegel auch auf mehreren Eingängen.> Weil du damit den S+H Kondensator auflädst und an den anderen offenen> Eingängen hängt nichts, was ihn wieder entlädt.> Schau Dir das mal an:> https://www.gammon.com.au/images/Arduino/ADC_internals.png
Ok, also das Bild bzw. den Aufbau kannte ich. Allerdings bin ich kein
Elektroniker und weiß nix über die Seiteneffekte, die Ihr auf den ersten
Blick seht.
Ich kann ein Datenblatt lesen und eine Firmware dazu schreiben. Das
Optimieren der Hardware muss ich anderen überlassen :(
Aber danke für den Hinweis! Dann werde ich noch etwas mehr mit den
Eingängen experimentieren.
> Wenn zur Compile-Zeit eh alles feststeht wirds mit templates kompakter> und schneller.
Hm, also wenn ich Funktionsaufrufe vermeide gewinne ich auf jeden Fall
Laufzeit. Den Bedarf an templates habe ich bislang noch nicht gesehen.
Vielleicht sehe ich den in der zweiten Runde?
Abgesehen davon gefällt mir auch die Syntax der templates bei der
Verwendung nicht. Ich finde es nicht lesbarer.
Die Kapselung der Daten kostet auf jeden Fall Laufzeit und
klassenübergreifend können Speicherbereiche nicht gepackt werden, d.h.
ich verliere Speicher durch Lücken zwischen den Klassen.
Beides Probleme, die durch templates nicht gelöst werden.
> Eiegntlich bin ich ein großer Freund von C und habe C++ für Controller
Ich liebe das Wort "eigentlich" :D
> Deswegen meine Frage: Wie ist denn das Verhalten von C++ zu C in Bezug> auf Programmgröße und Ausführgeschwindigkeit?
Nun, es dürfte klar sein, dass man sich in beiden Aspekten Nachteile
erkauft. Je nachdem, wie gut man in C programmiert hat.
Ich konnte bei mir feststellen, dass C++ ungefär 30% mehr Platz braucht
und in der Ausführungsgeschwindigkeit gibt es auch deutliche Nachteile.
Zumindest wenn man effizient in C programmiert hat.
Was man gewinnt ist Lesbarkeit, Flexibilität und Übersichtlichkeit.
Wobei der Gewinn an Flexibilität sicher am größten ist. Ich finde es
auch wesentlich angenehmer und weniger fehleranfällig, für
Pin-Zuordnungen nur noch eine Zeile zu haben (gegenüber 3 in C).
Ob das die Nachteile aufwiegt, muss sicher von Fall zu Fall abgewägt
werden.
Pauschale Aussagen sind selten richtig.
Mit 30% mehr Code Größe kann ich gut leben, besonders bei den fetten
AVR.
Aber bei den kleinen Tiny stellt man sich ja schon manchmal die Frage ob
Assembler notwendig wird. Da wird es mit C++ schon mal gar nicht gehen.
Das Beste ist sicher, wenn man jede Lib erst mal in C implementiert und
nur bei Bedarf auch in C++ Klassen kapselt.
Auf der anderen Seite wird man immer bei C bleiben, wenn alles benötigte
in C vorhanden ist und in C++ erst implementiert werden müsste.
Andere Frage: Wenn ich nur die Dateiendung auf .cpp ändere und weiterhin
C Code schreibe wie bisher, dann müsste doch die selbe Größe und
Laufzeit raus kommen?
Wenn man dann auf String und das übrige Gefrickel verzichtet und nur ab
und zu eine Klasse verwendet um Zusatz Hardware zu abstrahieren, dann
sollte das eigentlich ein guter Kompromiss sein.
Klassen sind eigentlich das einzige was mir in C WIRKLICH fehlt. Und die
auch nur für ganz bestimmte Dinge wie Hardware Module.
Thomas W. schrieb:> Andere Frage: Wenn ich nur die Dateiendung auf .cpp ändere und weiterhin> C Code schreibe wie bisher, dann müsste doch die selbe Größe und> Laufzeit raus kommen?
Versuch macht klug,
das sollte so stimmen. Aber der C++ Compiler übersetzt manche "C" -
Konstrukte nicht. Und auch beim mischen von C und C++ kann es passieren
das der Linker durch name-mangling
https://en.wikipedia.org/wiki/Name_mangling
nicht mehr alles zusammen passt.
C++ Compiler bringen aber auch ein wenig was bei "reinem" C.
So sind Funktionen uberladungen möglich und default parameter gehen
auch.
> Aber bei den kleinen Tiny stellt man sich ja schon manchmal die Frage ob> Assembler notwendig wird. Da wird es mit C++ schon mal gar nicht gehen.
Wie gesagt - ich will keine eierlegende Wollmilchsau.
... und für die kleinen Tinys finde ich C++ sowieso fehl am Platz.
> Wenn man dann auf String und das übrige Gefrickel verzichtet und nur ab> und zu eine Klasse verwendet um Zusatz Hardware zu abstrahieren
Nun, dann schau Dir mal die Klassen an. Da gibt es keine String-Klasse!
Wozu auch!
Arduino ist für mich ein Beispiel, wie man es nicht machen sollte. Die
Abstraktion nimmt jeden Bezug zum Datenblatt und man erkauft sich jede
Menge Nachteile, ohne wirklich Vorteile zu erhalten.
Nee, dafür ist mir meine Zeit zu schade!
Von der ganzen eingangs zitierten Diskussion hat mich im Grunde nur der
Beitrag von Karl Heinz Buchegg überzeugt. Die Pinklasse ist so, wie ich
eine Abstraktion auch anwenden möchte.
An die Vorlage habe ich meine C-Module angepasst. Erstmal nur um die
Funktionalität zu erreichen. Jetzt kommt die Arbeit, zu schauen, wo sich
was optimieren lässt.
Um den Sinn von C++ nachvollziehen zu können, muss man sich nur mal
anschauen, wieviel Aufwand ist es, einen weiteren Taster oder noch
besser, einen weiteren Drehencoder, vielleicht sogar mit anderem
Tastverhalten einzubinden.
In beiden Fällen seien die erprobten Dateien für einen Taster bzw.
Encoder vorhanden.
Ach ja - Taster und Encoder setzen beide auf Interrupt-Verarbeitung.
Bei diesem Vergleich ist C++ (für mich) der ganz klare Sieger. Auch wenn
ich den Encoder an einen anderen Port hängen muss, weil ich die
verwendeten Pins anderweitig nutzen will - auch hier (für mich) C++ ganz
klar im Vorteil.
Deshalb ist das auch meine Stoßrichtung. Kleine Klassen, die man einfach
verwenden kann und bei deren Verwendung man weniger Fehler machen kann,
als in der C-Variante.
Kritisch wird es erst, wenn der Platzverbrauch die 90% überschreitet
oder die Ausführungsschritte nicht mehr in der benötigten Zeit
abgearbeitet werden können.
Holzer schrieb:> Das:> Reinhard M. schrieb:>> Vor dem>> Optimieren sollte es erstmal zuverlässig laufen.>> stimmt mehr als das>> rmu schrieb:>> Mit den virtuellen Methoden etc.. würd ich aufpassen, das braucht viel>> Platz und hat auch Laufzeit-Overhead.>> also erst weitermachen und wenn du dann Platz oder Laufzeit Probleme> bekommst, kannst du immer noch schauen.
Die Frage ob man eine Bindung zur Laufzeit oder Compilezeit vornimmt ist
eine Frage des Designs, nicht der Optimierung. Polymorphismus als
Selbstzweck sieht zwar vielleicht nett aus, ist aber schädlich für
Codesize, Laufzeit und Wartbarkeit.
> Wie ist denn das Verhalten von C++ zu C in Bezug> auf Programmgröße und Ausführgeschwindigkeit?
Es kann auf ungefähr 0% Overhead hinaus laufen. Nämlich wenn du auf
virtuelle Methoden, Exceptions und dynamische Speicherverwaltung
komplett verzichtest.
Stefan U. schrieb:>> Wie ist denn das Verhalten von C++ zu C in Bezug>> auf Programmgröße und Ausführgeschwindigkeit?>> Es kann auf ungefähr 0% Overhead hinaus laufen. Nämlich wenn du auf> virtuelle Methoden, Exceptions und dynamische Speicherverwaltung> komplett verzichtest.
Es kann durchaus auch kleiner/schneller werden, sogar mit Exceptions,
das kommt aber ganz auf die Anwendung drauf an.
Mit Exceptions kann man die Behandlung von Fehlern "zentralisieren".
Fehlermeldungen über Rückgabewerte zu machen zwingt zu Fehlerabfrage
nach jedem Funktionsaufruf. Disclaimer: ich hab keine Ahnung, ob der GCC
auf AVR exceptions unterstützt und wie die dort implementiert sind, auf
ARMs (M0 aufwärts) funktionert das aber ausgezeichnet und ohne
Laufzeit-Overhead im nicht-exception-Fall.
Mit templates kann man Effekte erzielen die in C nur mit Makros möglich
sind. Es gibt sicher einiges was von der Sprache und von den
Implementierungen bei den templates nicht optimal gelöst ist, lesbarer
und wartbarer als Makros sind sie aber alle mal.
Das Hauptproblem ist meiner Meinung nach, dass viele Leute C++ nur
als ein "C mit Klassen" ansehen und es dementsprechend auch nur wie
ein "aufgebohrtes C" verwenden.
Wer C++ aber als eigenständige Sprache betrachtet und die gebotenen
Möglichkeiten vernünftig einsetzt, wird feststellen, dass ein C++-
Compiler durchaus deutlich effizienteren Maschinen-Code erzeugen kann
als ein C-Compiler. Ja, das ist wirklich so. Wer es nicht glaubt
(und das dürften wohl die wenigsten der C-Verfechter), kann sich
für den Anfang ja mal das hier ansehen und wirken lassen:
https://www.youtube.com/watch?v=zBkNBP00wJE
> Polymorphismus als Selbstzweck sieht zwar vielleicht nett aus, ist aber> schädlich für Codesize, Laufzeit und Wartbarkeit.
Einverstanden. Wie gut, dass ich nix zum Selbstzweck mache :D
Ich habe bislang wenig template-Beispiele gesehen, die mich animiert
hätten, es nach zu machen. Bei den meisten template-Geschichten kam es
mir so vor, als wenn Blinde über Farben philosophieren. Wenn es dann
wirklich mal ans Eingemachte geht, wird es schnell sehr ruhig in der
template-Fangemeinde.
> Es kann durchaus auch kleiner/schneller werden, sogar mit Exceptions,> das kommt aber ganz auf die Anwendung drauf an.
Na dann liefere doch mal ein Beispiel.
Ich habe hier ein ganz triviales Beispiel zusammen gestrickt - ohne
Selbstzweck, ohne templates, ohne virtuelle Funktionen ...
Einfach nur zwei Pins die regelmäßig umgeschaltet werden.
Über das Auskommentieren von defines lassen sich unterschiedliche
Varianten übersetzen. Jeweils mit den gleichen Compiler-Schaltern.
Die Plain-C Variante kommt auf 164 byte program und 0 byte data
Die C++ Variante mit der Pin-Klasse von Meister buchegg kommt bei mir
auf 380 bytes program und 24 bytes data
Der Versuch einer Optimierung kommt auf 308 byte program und 0 byte
data.
Leider weiß ich nicht, wie Meister Buchegg zu seinem knackigen Code kam.
Bei mir will das nicht so richtig.
Leider folgt der compiler (avr-g++ 4.8.1) nicht den inline Vorgaben und
macht eigenmächtige Funktionen draus, sodass die C++-Variante auch
Laufzeit-Einbußen verzeichnen muss.
Wenn mir also jemand erklären könnte, wie ich dem Compiler abgewöhnen
kann, einen Zeiger erst in 2 Register zu laden und dann den Wert zu
interpretieren, dem wäre ich sehr verbunden.
Auch sonstige weiterführende Tips werden gerne angenommen. Ich lerne
gerne dazu :)
Auf theoretisches Vielosofieren habe ich keinen Bock.
Reinhard M. schrieb:> Ich habe hier ein ganz triviales Beispiel zusammen gestrickt
Entschuldige bitte, aber mit so einem Spaghetti-Code mit zig #ifdefs,
die nicht mal vernünftig eingerückt sind, möchte ich mich ehrlich gesagt
nicht großartig auseinander setzen. Und dein Makefile ist wohl auch eher
als Beitrag für den Code Obfuscation Contest gedacht. Es wäre vielleicht
zunächst sinnvoll, wenn du mit einem Minimalbeispiel anfängst und dich
dann schrittweise weiter vorarbeitest.
> Leider folgt der compiler (avr-g++ 4.8.1) nicht den inline Vorgaben
Wenn ich aus deinem Makefile schlau werde, benutzt du sowohl -Os als
auch den Debug Mode. Von daher wundert mich das jetzt nicht wirklich.
Also nochmal der Tip, lieber mit einem Minimalbeispiel anzufangen und
vor allem auch die Compiler Flags zunächst sparsam einzusetzen, bis du
wirklich durchsteigst, welches Flag welche Auswirkungen auf den
erzeugten Maschinencode hat.
Thomas W. schrieb:> Deine source files sind .c nicht .cpp
Seine Source Files sind nicht .c, sondern .C - das ist ein Unterschied!
Natürlich ist .cpp verbreiteter, aber .C ist ebenso erlaubt.
@avr-g++ Fanboy
Geiles video! Danke für den Link! Das muss ich mir in Ruhe nochmal
reinziehen.
> Entschuldige bitte, aber mit so einem Spaghetti-Code mit zig #ifdefs,> die nicht mal vernünftig eingerückt sind, möchte ich mich ehrlich gesagt> nicht großartig auseinander setzen.
Ja genau - war mir irgendwie klar. Keine Ahnung, aber wie soll man
sequentielle Zeilen einrücken?
> Und dein Makefile ist wohl auch eher als Beitrag für den Code Obfuscation> Contest gedacht.
LOL - das Makefile ist schon über 10 Jahre alt. Seinerzeit habe ich mir
die Entwicklungsumgebung für AVR-Projekte aufgesetzt. Ich weiß nimmer,
welches Hilfsmittel es war, aber die Makedatei wurde generiert.
Ich habe sie solange angepasst bis es nach meiner Vorstellung
funktionierte und seither wird die Datei von Projekt zu Projekt weiter
kopiert. Warum sollte ich was in Frage stellen, was funktioniert und ich
sowieso nicht besser machen kann?
Aktuell habe ich nur einige Stellen für die C++-Unterstützung angepasst.
Aber hey - man kann alles schlecht reden.
> Wenn ich aus deinem Makefile schlau werde, benutzt du sowohl -Os als> auch den Debug Mode.
Das sind sicher zwei paar Stiefel. Wenn ich mir die
Befehlszeilenausgaben anschaue, dann sehe ich da keine Debug-Optionen
> mit -O1 cmpiliert:
Hm, irgendwo las ich mal, dass man beim AVR die -O1 nicht verwenden
sollte, sondern nur -Os
Wenn sich daran was geändert hat, dann habe ich das nicht mitbekommen.
> Sieh Dir das folgende Beispiel an
Ganz klasse! Das ist jetzt nicht Dein Ernst, oder?
Was soll da der Vorteil Deiner Klassen sein?
Du verwendest das PORT-Makro in den Klassen-Funktionen und wenn Du jetzt
nen Pin von PortC verwenden willst, dann brauchst Du ne neue Klasse.
Ja, so macht der Einsatz von C++ wirklich Sinn =:O
Reinhard M. schrieb:> @avr-g++ Fanboy>> Entschuldige bitte, aber mit so einem Spaghetti-Code mit zig #ifdefs,>> die nicht mal vernünftig eingerückt sind, möchte ich mich ehrlich gesagt>> nicht großartig auseinander setzen.>> Ja genau - war mir irgendwie klar. Keine Ahnung, aber wie soll man> sequentielle Zeilen einrücken?
Mit Tabs oder Leerzeichen.
>> Sieh Dir das folgende Beispiel an>> Ganz klasse! Das ist jetzt nicht Dein Ernst, oder?> Was soll da der Vorteil Deiner Klassen sein?> Du verwendest das PORT-Makro in den Klassen-Funktionen und wenn Du jetzt> nen Pin von PortC verwenden willst, dann brauchst Du ne neue Klasse.> Ja, so macht der Einsatz von C++ wirklich Sinn =:O
Ehrlich gesagt frage ich mich schon, ob du nur trollen willst oder
wirklich nicht zu dieser einfachen Transferleistung im Stande bist!?
Falls du kein Troll sein solltest, möchte ich dir nahelegen,
deine generelle Herangehensweise an solche Themen zu hinterfragen.
In der Form ist das nämlich sicherlich nicht zielführend.
Die Code-Größe ändert sich dadurch übrigens um kein einziges Byte.
Und natürlich ist das Beispiel immer noch trivial. Es bringt aber
doch nichts, hier seitenlangen "Produktiv-Code" runterzuleiern,
den du am Ende erst recht nicht nachvollziehen können wirst.
> irgendwo las ich mal, dass man beim AVR die -O1 nicht> verwenden sollte, sondern nur -Os
Das kommt ganz darauf an, wie du den Code optimiert haben möchtest. -Os
minimiert den Flash bedarf. Der Schuss KANN allerdings nach hinten los
gehen und mit schlechter Performance und übermäßigem Stack Bedarf enden.
Muss nicht, aber kann.
-O1 optimiert den Code auf Performance. Auch dies KANN negativ enden,
nämlich mit übermäßigem Flash Bedarf.
Häufig liefern beide Varianten sehr ähnlichen Code. Meistens liefern
beide Varianten ein brauchbares Ergebnis.
>> Sieh Dir das folgende Beispiel an> Ganz klasse! Das ist jetzt nicht Dein Ernst, oder?> Was soll da der Vorteil Deiner Klassen sein?
Die Klasse soll gar nicht Vorteilhaft sein. Mein Beispiel sollte nur
zeigen, wie viel Overhead C++ kostet, wenn man es sparsam einsetzt.
Hi,
Der Thread hat mich jetzt dazu gebracht mich hier anzumelden...
Ich bin auch seit ein paar Wochen dabei mich einzuarbeiten in Elektronik
und Mikrocontroller und hab nen ziemlich starken C++ Hintergrund - also
musste ich das natuerlich als erstes ausprobieren ;)
Ich leg meine Version hier einfach mal mit hin als Beispiel fuer nen
anderen Ansatz mit C++.
Die Version setzt stark auf templates und inlining - keine exceptions,
laufzeitpolymorphie oder degleichen.
Ausserdem ist das alles header only, also wird nix sinnlos dazugelinkt.
Ziel ist geringe Codegroesse und Typsicherheit - aber ich bin natuerlich
noch am Anfang und noch net so ganz drin in der Materie...
test.cpp hoert auf interrupts, kommuniziert ueber uart, steuert ein
display an ueber spi, liest den adc aus, macht pwm und benutzt einen
timer.
Will jetzt deswegen keinen neuen Thread aufmachen, aber vielleicht
passts ja hierhin, eben als beispiel fuer c++ (mit template magie),
nicht "c mit klassen"
Okay, ihr habt mich vollkommen überzeugt von C++.
Meine Vorurteile muss ich über Board werfen.
Nach 4 Stunden probieren und testen kann ich sagen, dass man absolut
verlustfrei in C++ coden kann für AVR µC, egal welche Sorte.
Vielen Dank für die Inspiration :-)
Den folgenden Talk von der CppCon fand ich sehr spannend:
https://www.youtube.com/watch?v=zBkNBP00wJE&list=PLHTh1InhhwT7J5jl4vAhO1WvGHUUFgUQH&index=4
Dabei zeigt der Kollege, welche Abstraktionen alles möglich sind, die
vom Compiler dann z.T. komplett weg optimiert werden.
Heute hatte ich auch wieder so ein Erlebnis: uController Hersteller
erstellt für seine Controller eine C-Abstraktions-Library. Die hat eine
Funktion, um die 3 Parameter einer PLL zu ermitteln. Die Funktion wird
beim Start immer mit dem selben Parameter aufgerufen und liefert auch
immer das selbe Ergebnis. Die Implementierung iteriert mit drei
verschachtelte Schleifen über alle Möglichen Werte.
In C++ könnte man das als constexpr functions implementieren und der
compiler würde dann das Ergebnis zur compiler-Zeit berechnen.
mfg Torsten
@David Uebler
Ich habe mir Deine Magie runtergeladen und angeschaut.
Die Klassen sind ja ganz ordentlich geschrieben. Hast Dir viel Arbeit
gemacht.
Wenn ich mir allerdings die "Projekt"-Dateien anschaue, dann frag ich
mich schon: und wo liegt jetzt genau der Vorteil?
Die test.cpp sieht grausig aus und ich kann keinen Vorteil gegenüber
einer C-Variante entdecken. Es ist weder lesbarer, noch
fehlertoleranter, noch kürzer. avr-g++ Fanboy müsste das eigentlich auch
wieder als Spaghetti-Code bezeichnen.
Ähnlich sieht es mit dem Makefile aus. Scheinbar lesbarer, aber nicht
für andere Projekt verwendbar. Das Makefile dagegen, das ich verwende,
ist jederzeit für jedes Projekt verwendbar. Minimalste Anpassungen und
es ist wieder verwendbar. Da hat sich mal ein erfahrener Entwickler
(nicht ich) viel Gedanken gemacht und es ausgetüftelt. In meiner
Variante habe ich zudem eine Trennung von Quellcode und Binärdateien -
etwas, was mir auch sehr wichtig ist.
Last not least habe ich mir die lss-Datei angeschaut. Und die reißt mich
auch nicht wirklich vom Hocker. Da sind jede Menge Funktionsaufrufe und
die Interrupt-Routinen beinhalten auch nur Funktionsaufrufe - also
eigentlich etwas, was man vermeiden möchte. Durch den Funktionsaufruf
aus der Interrupt-Routine heraus kann der Compiler die Register nicht
mehr überwachen und muss alle sichern und wieder herstellen. Auch wieder
suboptimal.
Somit hat Dein Upload wieder mein Vorurteil bestätigt: template-Magie
sind Geschichten, an denen sich Theoretiker aufgeilen können, aber für
den Praktiker wenig hilfreich/überzeugend
Stefan U. schrieb:> Und jetzt in C++ mit inline:> -> 62 Bytes, Überraschung!!! 0% Overhead
(Übergabe von PORTB als uint8_t geht übrigens so nicht.)
Aber: mach zum Vergleich mal eine Version mit template, vor allem die
Pin-Nummer als Template-Parameter.
Die reduziert sich dann auf wenige Bytes, effektiv wird gehe_an und
gehe_aus dann jeweils zu einem einzigen ASM-Befehl.
Weil "1<<variable" auf dem AVR als Schleife implementiert ist, spart
allein die zur Kompilezeit bekannte Pin-Nummer viel Rechenzeit und
Flash, das sollte aber auch bei der inline/const-member Version schon
klappen.
Wie sich heraus stellte, kann der avr-gcc in Version 4.8.1 die
Optimierungen der PC-Variante gleicher Version noch nicht. Es wird also
noch eine Weile dauern, bis man die Tips aus dem Video auch umsetzen
kann.
Die Klasse mit dem Zeiger braucht 334 bytes und die Klasse mit dem cast
326 bytes. Der Unterschied reißt es jetzt nicht wirklich raus und
wirklich leserlicher finde ich die Klasse mit dem cast auch nicht.
Die Leute, die meine Beiträge negativ bewerten dürften selbst mal die
Assemblerausgaben vergleichen und beurteilen. Ist meine Einschätzung
wirklich so daneben?
... und der nicht funktionierende Schwachsinn von hier ist dann besser?
Beitrag "Re: AVR und C++ - ein Versuch"
Planlos schrieb:> allein die zur Kompilezeit bekannte Pin-Nummer
Die muss ja aber nicht immer zur Compiletime bekannt sein. Damit duerfte
sich dieser "Vorteil" dann doch wieder erledigen, oder nicht? :o
Also das Makefile ist doch ok: mehr braucht es doch für ein Projekt mit
einer header-only-template-lib nicht!
Ein C++-Code, der auf Heap, RTTI und runtime-polymorphism und exceptions
verzichtet und stattdessen parametric-polymorphism zusammen mit ein paar
patterns wie monostate verwendet, dabei mindestens C++14 ist, und auch
neue features wie closures und variadic templates benutzt, ist m.E. sehr
gut für bare metal geeignet. Und produziert keinesfalls größe
executables als C und der Verbrauch an stack ist auch nicht größer
(eigentlich aheb ich eher die Erfahrung gemacht, dass die Größen
schrumpfen).
Leider gibt es keine wirklich ganz brauchbaren Bibliotheken (jedenfalls
für mich) und die Hersteller wie etwa Atmel mit den Tinys/Megas machen
einem das Leben wirklich nicht einfach den Code generisch für eine ganze
Familie von MCUs zu schreiben (ok, bei den XMegas sieht es etwas besser
aus).
VG
Wilhelm
Kaj schrieb:> Planlos schrieb:>> allein die zur Kompilezeit bekannte Pin-Nummer> Die muss ja aber nicht immer zur Compiletime bekannt sein. Damit duerfte> sich dieser "Vorteil" dann doch wieder erledigen, oder nicht? :o
Du weißt zur Compiletime nicht, wie Deine Hardware aussieht?
@Wilhelm M.
>Leider gibt es keine wirklich ganz brauchbaren Bibliotheken (jedenfalls>für mich) und die Hersteller wie etwa Atmel mit den Tinys/Megas machen>einem das Leben wirklich nicht einfach den Code generisch für eine ganze>Familie von MCUs zu schreiben
Sicher gibt es brauchbare Bibilotheken in C++
https://github.com/KonstantinChizhov/Mcucpp
Dauerte ein Weilchen bis ich <Template> & Co. verstanden habe.
Mcucpp ist ein riesiger Fundus und funktioniert prima. Die Beispiele
zeigen den Weg. Ohne vertieftes C++ Wissen muss man sich zuerst
einarbeiten und allenfalls Vorurteile beiseite legen. Aber vom Konzept
her bin ich überzeugt, dass modular aufgebauter C++ Code besser lesbar
und universeller verwendbar ist als Plain_C.
Beispiel:
********
MAX7219 / MAX7221 8-Digit LED Display Driver
- Übergabe der uC_Pins und div. Parameter als <template>
- Erstellen eines CTRL-Bus mittels eine Pinlist
-.h(pp) File enthält die Klasse und meist auch alle Methoden.
Die Initialisierung in Projekt.cpp sieht dann so aus
//display driver
// *******************************************
typedef cMax7219 <
Pb1, // DIN
Pb3, // LOAD
Pb2, // CLK
8, // digits
1 // intensity
// default // decode
// *******************************************
>_Display;
Ja, die (Deine?) Bibliothek kenne ich. Die finde ich auch im Ansatz ganz
gut! Was ich meinte war etwas wie die avr-libc, die als größeres
OSS-Projekt (dauerhaft) Bestand hat.
MCUCPP ist nicht von mir,
so tief werde ich C++ nie verstehen,
Link und Author stehen ja im Beispiel.
Wenn ich es richtig verstehe, ist AVR-Lib eine vorkompilierte Bibliothek
mit Header-Dateien .h und vorkompilierten Dateien .S.
Da ist nichts C++ und wird vermutlich auch noch länger nicht sein.
Ob man so eine Bibliothek ohne C_Sourcen irgendwie in ein C++_Frame
einbinden kann weiss ich nicht.
MCUCPP schrieb:> MCUCPP ist nicht von mir,
ah, das hatte ich auch nur anhand des nickname vermutet ;-)
> so tief werde ich C++ nie verstehen,> Link und Author stehen ja im Beispiel.>> Wenn ich es richtig verstehe, ist AVR-Lib eine vorkompilierte Bibliothek> mit Header-Dateien .h und vorkompilierten Dateien .S.
MCUCPP benutzt auch avr-libc wie jedes Programm, das mit avr-gcc
erstellt wird.
>> Da ist nichts C++ und wird vermutlich auch noch länger nicht sein.
Ist ja auch eine C-Bibliothek.
Mit C++ Templates habe ich mich bisher kaum beschäftigt. Diese mächtige
Tool ist super interessant, leider ist die Syntax für mich sehr schwer
zu verstehen. Vom Prinzip her schon, aber was da genau im Hintergrund
passiert ...
Habe ich das richtig verstanden? Diese enum{} Sachen dienen vorallem
dazu, dass das "Objekt" keinen Patz im Speicher braucht. Sonst könnte
man ja auch einfach Variable verwenden.
Könnte man denn damit das Port und die Bitnummer eines digitalen Ein-
oder Ausgang "speichern"? Zusammen mit inline Funktionen würde dann
niemals wirklich RAM belegt. Das Objekt wäre rein virtuell und doch
könnte man den Ausgang auf high setzen, togglen oder auf low setzen,
Eingänge abfragen etc. etc.
Bisher habe ich IO per #define definiert und über inline Funktionen
bedient.
#define LED3 (PORTC, 3)
Schöner wäre natürlich:
dInput button1(PORTC,1);
dOutput led3(PORTC,3);
Ja diese MCUCPP ist sehr schön gemacht. Danke für den Link.
So, ich denke, ich habe den Kasus Knaxus gefunden. Die Pin.h sieht jetzt
zwar übel aus, aber da das Stoff ist, den man normalerweise von Projekt
zu Projekt ohne Änderung kopiert, ist mir das egal.
Der Teil, der in jedem Projekt neu geschrieben werden muss, ist mir
wichtiger und der soll sauber aussehen.
Der Vergleich:
Plain C ergibt 130 byte program
1
#include<avr/io.h>
2
3
intmain(void){
4
DDRA|=1<<PA1;
5
DDRC&=~(1<<PC5);
6
7
// main loop
8
for(;;){
9
if(PINC&(1<<PC5))PORTA&=~(1<<PA1);
10
elsePORTA|=1<<PA1;
11
}
12
}
C++ hat lesbare Pin-Klassen, die auch weiter verwendet werden können und
braucht genau 8 byte mehr.
1
#include"Pin.h"
2
3
usingnamespacesrd;
4
5
intmain(void){
6
PortA::Pin1led;
7
PortC::Pin5check;
8
9
led.toOutput();
10
check.toInput();
11
12
// main loop
13
for(;;){
14
if(check.isHigh())led.setLow();
15
elseled.setHigh();
16
}
17
}
Die einzelnen Pin-Anweisungen sind jetzt in C und C++ identisch (auch in
der lss-Datei.
Für mich eine Basis, den Rest daran anzupassen :)
Wenn man mehrere Software I2C braucht verwende ich gern die Fleury .s
Die ist halt so klein und funktioniert sehr gut.
Man muss da halt das .s File duplizieren und die IO sowie die
Funktionsaufrufe anpassen.
Super genial wäre eine C++ Kapselung der Assembler Routinen in eine
Template Klasse, sodass der Assembler Code automatisch modifiziert wird.
Die Aufrufe gehen dann elegant über das jeweilige I2C Objekt.
@Reinhard M.
> Wenn ich mir allerdings die "Projekt"-Dateien anschaue, dann frag ich> mich schon: und wo liegt jetzt genau der Vorteil?
siehe ganz unten in meiner Antwort
> Die test.cpp sieht grausig aus und ich kann keinen Vorteil gegenüber> einer C-Variante entdecken. Es ist weder lesbarer, noch> fehlertoleranter, noch kürzer. avr-g++ Fanboy müsste das eigentlich auch> wieder als Spaghetti-Code bezeichnen.
Ja. Die sieht grausig aus. Aber ich bin auch noch net fertig ;)
Es fehlt noch ein Konzept aus Benutzer Sicht, eine art API.
Habs wie gesagt erst angefangen, also work in progress.
Im Moment ist das wirklich nur zum testen der ganzen Funktionen.
> Ähnlich sieht es mit dem Makefile aus. Scheinbar lesbarer, aber nicht> für andere Projekt verwendbar. Das Makefile dagegen, das ich verwende,> ist jederzeit für jedes Projekt verwendbar. Minimalste Anpassungen und> es ist wieder verwendbar. Da hat sich mal ein erfahrener Entwickler> (nicht ich) viel Gedanken gemacht und es ausgetüftelt.
Naja das Makefile solls ja auch nur bauen. Du kannst auch dein Makefile
fuer meinen Code benutzen, ist ja header only.
> In meiner Variante habe ich zudem eine Trennung von Quellcode und Binärdateien -> etwas, was mir auch sehr wichtig ist.
Trennung von Binaerdateien und Quellcode? versteh nicht was du meinst.
> Last not least habe ich mir die lss-Datei angeschaut. Und die reißt mich> auch nicht wirklich vom Hocker. Da sind jede Menge Funktionsaufrufe [...]
Kommt auf die optimierungsstufe an. mit O[1/2/3] sind die allermeisten
weg.
Os schaut vorallem auf die codegroesse, und da koennen sie Sinn machen.
Will man also nicht immer vermeiden denke ich.
Os: 69 calls
01: 15 calls
02: 11 calls
O3: 12 calls
> die Interrupt-Routinen beinhalten auch nur Funktionsaufrufe - also> eigentlich etwas, was man vermeiden möchte. Durch den Funktionsaufruf> aus der Interrupt-Routine heraus kann der Compiler die Register nicht> mehr überwachen und muss alle sichern und wieder herstellen. Auch wieder> suboptimal.
Bitte schau genauer hin:
Es gibt drei ISRs. Zwei davon sind explizit dynamisch. Das hab ich extra
so eingebaut. Die gehen ueber eine Sprungtabelle (siehe icall) und
koennen zur Laufzeit umgebogen werden (irq::attach<...>(..)) - ist also
Absicht.
Einer, der Timer, ist nicht dynamisch und wird geinlined, also kein
call.
> Somit hat Dein Upload wieder mein Vorurteil bestätigt: template-Magie> sind Geschichten, an denen sich Theoretiker aufgeilen können, aber für> den Praktiker wenig hilfreich/überzeugend
Auf den ersten blick, vielleicht.
Aber es gibt einfach viel mehr optimierungspotential fuer den Compiler
auf die Tour. Meine ganzen "Klassen" zb. haben keinen zustand, und das
ist echt was wert aus compiler sicht. Es gibt so nur dann branches und
calls wenn es notwendig ist.
Ausserdem etwas das mir persoenlich ganz wichtig ist: Typsicherheit.
Es gibt in meinem Beispiel extrem viele aufzaehlungen (timer waveforms,
pwm modi, twi master und slave, etc.) und alles ist in typen gegossen,
die gleichzeitig einen wert haben. Sobald der benutzer etwas falsch
macht krachts beim bauen.
Wenn du genau hinschaust wird dir auffallen dass ALLE dateien nichts
ueber den konkreten mikrocontroller wissen. nur in der atmega328.hpp
steht welche bits sich auf was beziehen. und eben diese masken werden in
den rest eingesetzt. Damit gibt es eine zentrale stelle die den
mikrocontroller beschreibt und dessen eigenheiten in den rest zur
compilezeit einbaut und zwar ohne ifdefs.
Das mag jetzt was sein was mich "aufgeilt" aber ich find das machts
sauberer.
Aber eins: Danke fuer den Hinweis, dass die cpp schlimm aussieht. das
stimmt absolut und ich muss mir echt ne nutzerschicht ueberlegen die
einheitlich ist. wird nochmal schwer. da sieht deine version schoener
aus.
Sheeva P. schrieb:> Kaj schrieb:>> Planlos schrieb:>>> allein die zur Kompilezeit bekannte Pin-Nummer>> Die muss ja aber nicht immer zur Compiletime bekannt sein. Damit duerfte>> sich dieser "Vorteil" dann doch wieder erledigen, oder nicht? :o>> Du weißt zur Compiletime nicht, wie Deine Hardware aussieht?
Doch, aber das heisst nicht, dass zur Compiletime auch die Pin-Nummer
bekannt sein muss, da diese vielleicht erst ueber eine Nachricht (UART,
Tastendruck, was auch immer) zur Laufzeit reinkommt.
Wilhelm M. schrieb:> Bei mir sieht sowas so aus:> [...]> EventHandlerParameter evp {> Timer::create(1000_ms, TimerFlags::Periodic)> };>> auto th = TimerHandler(evp);>> EventManager::run([&]() {}, th);> [...]
Wie sieht denn bei dir der EventManager aus? hoert der auf alle
interrupts?
oder hast du einen catch-all handler der das dann auseinanderpfrimelt?
Wuerd mich interessieren.
Thomas W. schrieb:> "PortA::Pin1 led;">> ist auch ein cooler Ansatz.
Nur sollte man sich an die Bezeichnungen im Datenblatt halten. Wenn dann
PA1 und nicht Pin1.
Und hier sieht man schon, dass OO-Abstraktion auf diesem Level absoluter
Humbug ist.
OOP auf Ebene der Applikation bringt einen echten Mehrwehrt aber doch
nicht die Abstraktion über das setzen eines Pins.
David U. schrieb:> Wilhelm M. schrieb:>> Bei mir sieht sowas so aus:>> [...]>> Wie sieht denn bei dir der EventManager aus? hoert der auf alle> interrupts?
Nein, in dem Beispiel liefert der HW-Timer AVR::Timer<0> einen ms-Tick
an den Event-Manager. Der Event-Manager ist der Dispatcher: holt einen
Event aus der Event-Queue und ruft den passenden Handler auf. Wobei das
Closure in
EventManager::run([&]() {}, th);
permanent aufgerufen wird. Aber so ein leeres Closure wird natürlich vom
Compiler wegoptimiert ...
> oder hast du einen catch-all handler der das dann auseinanderpfrimelt?> Wuerd mich interessieren.
Wilhelm M. schrieb:> David U. schrieb:>> Wilhelm M. schrieb:>>> Bei mir sieht sowas so aus:>>> [...]>>> [...]> Nein, in dem Beispiel liefert der HW-Timer AVR::Timer<0> einen ms-Tick> an den Event-Manager. Der Event-Manager ist der Dispatcher: holt einen> Event aus der Event-Queue und ruft den passenden Handler auf.
ah ok, verstehe. aber iwo musst du doch nen interrupt vektor aufsetzen.
wo passiert das?
Das schreibt man einfach in die (einzige) Implmentierungsdatei:
1
ISR(TIMER0_COMPA_vect)
2
{
3
Timer::timerTick();
4
}
Alles andere sind ja Header.
(Hatte mich oben vertan: die HW-Timer liefert den ms-Tick an den
SW-Timer. Läuft ein SW-Timer ab, so stellt er einen Event in die
EventQueue ...)
Wilhelm M. schrieb:> Das schreibt man einfach in die (einzige) Implmentierungsdatei:>>
1
>ISR(TIMER0_COMPA_vect)
2
>{
3
>Timer::timerTick();
4
>}
5
>
alles klar. ja hm. das nervt mich n bischen am avr-gcc.
der baut die vektor tabelle selbst und schaut nach funktionen die
__vector_N heissen.
laesst sich wohl auch kaum schoener machen ohne extra praeprozessor,
oder haste ne idee?
Ja klar, die Vektor Tabelle ist im Flash und muss in den SRAM kopiert
werden.
Aber was spricht dagegen die Vektoren trotzdem selbst zu setzen? Die
avr-libc kann man trotzdem verwenden.
Thomas W. schrieb:> Ja klar, die Vektor Tabelle ist im Flash und muss in den SRAM kopiert> werden.>> Aber was spricht dagegen die Vektoren trotzdem selbst zu setzen? Die> avr-libc kann man trotzdem verwenden.
hm geht das? wird die echt in den sram kopiert? dachte eigtl die liegt
nur im flash. wenns so waere koennte man die ja auch zur laufzeit
umbiegen.
hast du das schonmal ausprobiert?
David U. schrieb:> hm geht das? wird die echt in den sram kopiert? dachte eigtl die liegt> nur im flash. wenns so waere koennte man die ja auch zur laufzeit> umbiegen.> hast du das schonmal ausprobiert?
Nee, keine Ahnung. Ich habe mich darauf bezogen was Wilhelm M. schrieb:
Wilhelm M. schrieb:> Ich denke, solange man avr-libc verwendet, wird man es so machen müssen.> Denn der Start-Code crt1 überschreibt den InterruptVektor ...
Im datenblatt zum Mega1284P steht, die Interrupt tabelle steht an der
niedrigsten Adresse vom "program memory space".
Also Flash und daher schwer änderbar.
Thomas W. schrieb:> David U. schrieb:>> hm geht das? wird die echt in den sram kopiert? dachte eigtl die liegt>> nur im flash. wenns so waere koennte man die ja auch zur laufzeit>> umbiegen.>> hast du das schonmal ausprobiert?>> Nee, keine Ahnung. Ich habe mich darauf bezogen was Wilhelm M. schrieb:
Darüber habe ich nichts gesagt. Und ja, der Interruptvector steht im
Flash ab 0x0000.
>> Wilhelm M. schrieb:>> Ich denke, solange man avr-libc verwendet, wird man es so machen müssen.>> Denn der Start-Code crt1 überschreibt den InterruptVektor ...
Schlecht ausgedrückt: der Compiler schreibt in die nicht verwendeten
Dummy-Sprünge hinein.
>> Im datenblatt zum Mega1284P steht, die Interrupt tabelle steht an der> niedrigsten Adresse vom "program memory space".>> Also Flash und daher schwer änderbar.
Man könnte aber mal probieren, ob man eine statische Elementfunktion mit
dem _attribute_ signal austatten kann und ob die dann in den IVektor
kommt ...
Ggf. kann man auch noch auf den StartCode crt1 mit einer Option an den
Linker verzichten.
Versucht man einer statischen Elementfunktion das __attribute__(signal)
zu verpassen, sagt der avr-g++ (!!!), dass die ISR den falschen Namen
hat. Also, das wird man nicht so einfach ändern können.
Es sein denn, man schreibt gcrt.S um, so dass dort dann andere Namen als
__vector_1 etc. drin stehen.
Das lohnt m.E. den Aufwand nicht ...
Reinhard M. schrieb:> So, ich denke, ich habe den Kasus Knaxus gefunden. Die Pin.h sieht jetzt> zwar übel aus, aber da das Stoff ist, den man normalerweise von Projekt> zu Projekt ohne Änderung kopiert, ist mir das egal.
Vor einiger Zeit habe ich mir mal Gedanken darüber gemacht, wie man
Register und Pins etwas eleganter in C++ ausdrücken kann. Das Ergebnis
findest Du im Anhang in den Dateien "includes/Reg.hpp" und
"includes/Pin.hpp". Zudem habe ich in den Verzeichnissen "ex0" und "ex1"
zwei Beispiele beigefügt, die sich mit
1
avr-g++ -mmcu=atmega8 -Os -std=c++11 -I ../includes/ -o main main.cpp
Wilhelm M. schrieb:> Darüber habe ich nichts gesagt. Und ja, der Interruptvector steht im> Flash ab 0x0000.
Nein, nicht notwendigerweise. Die Interruptvectortabelle kann bei einem
ATMega üblicherweise an 5 verschiedenen Stellen im Flash liegen...
Sheeva P. schrieb:> Vor einiger Zeit habe ich mir mal Gedanken darüber gemacht, wie man> Register und Pins etwas eleganter in C++ ausdrücken kann. Das Ergebnis> findest Du im Anhang in den Dateien "includes/Reg.hpp" und> "includes/Pin.hpp". Zudem habe ich in den Verzeichnissen "ex0" und "ex1"> zwei Beispiele beigefügt, die sich mit>
1
avr-g++ -mmcu=atmega8 -Os -std=c++11 -I ../includes/ -o main
2
> main.cpp
> übersetzen lassen.
Danke für deine Code Inspiration.
Du speicherst 32 Bit pro Pin ab, etwas großzügig finde ich. Speziell da
man DDR und PIN aus PORT errechnen kann, die Register Offsets sind -1
und -2.
Mir gefällt es an sich, aber ich möchte auf null Byte Objektgröße
kommen.
Mittels Templates und Inline Funktionen.
Thomas W. schrieb:> Mir gefällt es an sich, aber ich möchte auf null Byte Objektgröße> kommen.> Mittels Templates und Inline Funktionen.
Genau: hier ein Beispiel (unvollständig, da die MCU Templates fehlen):
1
template<typenameMCUPort,typenameName>
2
structPort{
3
typedefMCUPortmcuport_type;
4
typedefNamename_type;
5
Port()=delete;
6
staticvoidset(uint8_tv){
7
getBaseAddr<MCUPort,Name>()->out=v;
8
}
9
staticuint8_tget(){
10
returngetBaseAddr<MCUPort,Name>()->out;
11
}
12
staticvoiddir(uint8_tv){
13
getBaseAddr<MCUPort,Name>()->ddr=v;
14
}
15
staticvolatileuint8_t&dir(){
16
returngetBaseAddr<MCUPort,Name>()->ddr;
17
}
18
staticuint8_tread(){
19
returngetBaseAddr<MCUPort,Name>()->in;
20
}
21
};
22
23
template<typenamePort,uint8_tPinNumber>
24
structPin{
25
static_assert(PinNumber<8,"wrong pin number");
26
Pin()=delete;
27
typedefPortport;
28
staticconstexpruint8_tnumber=PinNumber;
29
staticconstexpruint8_tpinMask=(1<<PinNumber);
30
staticvoidon(){
31
Port::set(Port::get()|pinMask);
32
}
33
staticvoidoff(){
34
Port::set(Port::get()&~pinMask);
35
}
36
staticvoidtoggle(){
37
Port::set(Port::get()^pinMask);
38
}
39
template<typenameDir>
40
staticvoiddir(){
41
if(Dir::value){
42
Port::dir()|=pinMask;
43
}
44
else{
45
Port::dir()&=~pinMask;
46
}
47
}
48
};
Für die Abstraktion von HW-REssourcen macht man keine Objekte, sondern
nicht instanziierbare class-templates.
Man könnte durch std::enable_if<> und entsprechende traits auch das
Laufzeit-if-statement entfernen. Doch da der Wert ja eh constexpr ist,
wird das vom Compiler weg optimiert!
Thomas W. schrieb:> Du speicherst 32 Bit pro Pin ab, etwas großzügig finde ich.
Das sieht nur so aus; tatsächlich optimiert der avr-g++ das restlos weg.
> Speziell da> man DDR und PIN aus PORT errechnen kann, die Register Offsets sind -1> und -2.
Das mag für heutige AVRs der Fall sein, muß aber nicht notwendigerweise
auch für zukünftige gelten.
> Mir gefällt es an sich, aber ich möchte auf null Byte Objektgröße> kommen.
Wie gesagt: laß' Dich nicht vom Quellcode täuschen.
> Mittels Templates und Inline Funktionen.
Ich bin gespannt, ob Dir das damit gelingen wird.
Moin moin,
> Es fehlt noch ein Konzept aus Benutzer Sicht, eine art API.
Ok, dann nehme ich alles zurück und behaupte das Gegenteil :D
Ein sehr geschätzter Ausbilder pflegte zu sagen: man darf einem Bauer
keine halbfertige Arbeit zeigen :O
>> In meiner Variante habe ich zudem eine Trennung von Quellcode und>> Binärdateien - etwas, was mir auch sehr wichtig ist.>> Trennung von Binaerdateien und Quellcode? versteh nicht was du meinst.
Kennst Du cmake? Dort ist es auch Konzept, dass man zum Bauen ein
build-Verzeichnis anlegt, wo dann die Binär-Dateien erzeugt werden. Ich
habe es einfacher gehalten und (weil ich auf Linux mit Befehlszeilen
arbeite, habe ich) ein Verzeichnis "nix" angelegt, in dem das Makefile
liegt. Dort rufe ich auch make auf. Die Quelldateien liegen im
Verzeichnis darüber.
Somit wird das Verzeichnis nicht durch Binär- oder Zwischen-Dateien
verunreinigt :)
Anm: Makefile mit nix-Verzeichnis ist im Paket aus erstem Beitrag
enthalten.
> Es fehlt noch ein Konzept aus Benutzer Sicht, eine art API.
Hm, da gehe ich einfach anders an die Geschichte heran. Ich komme aus
der Ecke Benutzer-Ergonomie und habe mir angewöhnt, als erstes die
Benutzerschnittstelle festzuklopfen. Genauso mache ich es auch beim
Entwurf von Objekten/Klassen - als erstes überlege ich mir das public
interface. Erst danach überlege ich mir, wie ich das umsetzen kann.
Ist vielleicht nicht der leichteste Weg, aber die Akzeptanz ist deutlich
höher.
> Kommt auf die optimierungsstufe an. mit O[1/2/3] sind die allermeisten> weg.
Ok, als ich mit AVR und Co anfing, hieß es noch, dass O[123]
falschen/fehlerhaften Code produzieren würde und dass man die
Optimierungsstufen möglichst vermeiden solle. Wenn die Compiler
inzwischen besser geworden sind, kann ich ja mal andere
Optimierungsstufen ausprobieren. So ein compiler, wie in dem Video der
cppcon wäre schon schick - wird aber sicher noch ein paar Jahre dauern
:(
> Bitte schau genauer hin:> Es gibt drei ISRs. Zwei davon sind explizit dynamisch. Das hab ich extra> so eingebaut.
Lach - das habe ich wohl gesehen, denn es war auch mein erster Ansatz.
Als ich dann aber sah, was der Compiler draus machte, dachte ich mir,
das ist bullshit, das muss anders gehen.
Dann habe ich mir angeschaut, was ich denn normalerweise in den
Interrupt packen würde: Tastenentprellung, Encoder-Auswertung,
Buzzer-Handling ...
Yo - und dann wurde mir klar, dass nix davon wirklich im ISR ablaufen
muss.
Deshalb habe ich z.B. die Clock-Klasse gestrichen und die Sekunden in
den SystemTicker mit rein gepackt. Jetzt zählt der SystemTicker Sekunden
und Millisekunden.
Letzteres ist als Auflösung fein genug für alle Arten von ISR-clients.
Ist in dem Paket aus dem ersten Post zu sehen.
Jetzt werden die Tasten in der Hauptschleife entprellt. Funktioniert
gut.
Ich betreibe einen mega16 mit 16MHz, was bedeutet, zwischen 2
Systemtick-Events können 16.000 Anweisungen ausgeführt werden.
In meinem Fall ist das üppig genug, um nix mehr in die ISR zu packen.
Heißt aber nicht, dass ich jetzt jede Menge überflüssige
Funktionsaufrufe akzeptieren würde.
> Meine ganzen "Klassen" zb. haben keinen zustand, und das> ist echt was wert aus compiler sicht. Es gibt so nur dann branches und> calls wenn es notwendig ist.
Hm, den Vorteil gibt es aber nur auf den ersten, flüchtigen Blick. Die
Anwendung braucht Zustände und die müssen irgendwo hingepackt werden.
Ich halte es nicht für benutzerfreundlich, wenn man sich als Anwender
der Klassen noch Gedanken darüber machen muss, welche Zustände die
Objekte der Klassen brauchen, um funktionieren zu können.
So ein Framework ist ein richtiges Stück Arbeit. Wenn ich mir das antue,
dann will ich aus Anwendersicht auch einen Vorteil von haben. Wenn
dagegen sowas rauskommt, wie die Arduino-Programmierschicht, dann lasse
ich lieber von vorn herein die Finger davon.
Ob die Klassen jetzt schick aussehen (Deine tun das, ganz ohne Frage!)
ist mir völlig schnuppe. Der Teil der Projekte soll doch möglichst ohne
Anpassung von Projekt zu Projekt kopiert werden. Also ist dort doch der
richtige Platz für schmutzige Hacks, um nachher sauberen Anwendungscode
zu erreichen. Gut, so ist zumindest meine Denke ...
... und wenn ich zurück denke, an die Fremd-Bibliotheken, die mir im
Laufe der Zeit ans Herz gewachsen sind, dann haben die genau die gleiche
Philosophie beherzigt :)
> Wenn du genau hinschaust wird dir auffallen dass ALLE dateien nichts> ueber den konkreten mikrocontroller wissen.
Ich habe es gesehen, frage mich aber: ist das für den Anwender eines
Frameworks wirklich wichtig?
Also mich interessiert es herzlich wenig, ich will mich eigentlich™ nur
mit der öffentlichen Schnittstelle des Frameworks auseinander setzen.
Außerdem - egal wie groß der Quellcode für einen AVR auch werden sollte,
er ist selbst auf einem älteren Rechner in einem Wimpernschlag
übersetzt. Warum also sollte ich mir da über irgendeine Abhängigkeit
einen Kopf machen?
Auf dem PC sieht es anders aus. Dort gibt es schon Übersetzungszeiten im
Stundenbereich. Da werden die Abhängigkeiten wichtiger. Zumindest aus
meiner Sicht ;)
> Nur sollte man sich an die Bezeichnungen im Datenblatt halten. Wenn dann> PA1 und nicht Pin1.>> Und hier sieht man schon, dass OO-Abstraktion auf diesem Level absoluter> Humbug ist.
Hach - das sehe ich natürlich ganz anders - so rein subjektiv.
Ich halte es für natürlich, dass ein Port Pins hat. PA1 ist doppelt
gemoppelt. PortA sagt doch schon, dass es sich um den Port A handelt.
Wieso sollte ich dann beim Pin nochmal die Abhängigkeit A in den Namen
packen?
In C mit den #defines muss das sein. In C++ kann man drauf verzichten.
Mir gefällt mein Ansatz besser (logisch, sonst hätte ich es nicht so
gemacht) :D
... und ich werde auch ganz sicher nicht irgendwelche Registernamen in
die Benutzerschnittstelle hochziehen. Die haben da imho nix mehr
verloren.
> alles klar. ja hm. das nervt mich n bischen am avr-gcc.> der baut die vektor tabelle selbst und schaut nach funktionen die> __vector_N heissen.> laesst sich wohl auch kaum schoener machen ohne extra praeprozessor,> oder haste ne idee?
Schau mal im Paket aus dem ersten Beitrag die Datei SystemTicker.h -
dort ist der ISR-handler "versteckt"/codiert. Der notwendige Hack für
avr-gcc taucht sonst nirgendwo mehr auf.
Ich kann damit leben ;)
Reinhard M. schrieb:>> alles klar. ja hm. das nervt mich n bischen am avr-gcc.>> der baut die vektor tabelle selbst und schaut nach funktionen die>> __vector_N heissen.>> laesst sich wohl auch kaum schoener machen ohne extra praeprozessor,>> oder haste ne idee?>> Schau mal im Paket aus dem ersten Beitrag die Datei SystemTicker.h -> dort ist der ISR-handler "versteckt"/codiert. Der notwendige Hack für> avr-gcc taucht sonst nirgendwo mehr auf.> Ich kann damit leben ;)
Das mit der gefakten ISR ist ja eine gute Idee!
BTW:
Dann habe ich mal die Klasse SystemTicker angesehen. Du verwendest da im
Prinzip ein monostate-pattern. Allerdings funktioniert Deine Klasse nur,
wenn die erste Instanz weiterhin auf dem Stack liegt - wie in Deiner
main(), die ja nie verlassen wird. Denn Du speicherst einen this-Zeiger
in dem statischen Datenelement ticker. Du solltest stattdessen eine
Instanz in ticker erzeugen. Würdest Du den ctor vonm (ersten)
SystemTicker verschachtelt in einer Funktion aufrufen, so wird das darin
enthaltene SystemTicker-Objekt am Ende der Funktion ja zerstört und
somit ist this dangling. Dies ist konzeptionell falsch - ich vermute
aber, dass es trotzdem funktionieren würde, weil Du ja keine non-static
Datenelemente hast.
Im übrigen ist Deine Lösung auch nicht reentrant/thread-safe. Aber das
spielt hier wohl kein Rolle.
Wenn Du auf den Zeiger verzichtest, brauchst Du auch die Prüfung für den
Zeiger nicht mehr.
Die fragwürdige Stelle:
@Wilhelm M.
Du hast völlig recht! Mit allem :)
... aber wie Du schon selbst angemerkt hast: main wird ja nie verlassen.
Und wenn, ist das sowieso ein fataler Fehler. Deshalb sehe ich an der
Stelle keinen Handlungsbedarf.
Außerdem sollte ein Systemticker imho vor der Schleife des
Hauptprogrammes erzeugt werden.
Die Singleton-Reste könnte ich dagegen rausschmeißen. Sind noch aus der
Zeit, da ich Funktionaliät anderer Klassen wie bei einem Eventhandler
einbrachte. Da ich das aber nicht mehr machen will ...
...ok, hast mich überzeugt. Ich schmeiß es raus :)
//Edith:
der letzte Absatz war Kwatsch mit Source :(
Da waren die Finger schneller, als das Hirn. Ich schmeiß das deshalb
nicht raus, weil ich die SystemTicker-Klasse auch als Handle verwenden
wollte, um auf Daten zugreifen zu können. Wenn jemand ne Handle-Instanz
anlegt, soll er aber nicht den Timer initialisieren. Deshalb die
Singleton-Instanz. Weiß noch nicht, ob ich das so lasse ...
Ich habe jetzt den Rest an die neue Pinklasse angepasst und wie es
aussieht, ist die neue Pinklasse doch nicht so dolle :(
Während das Beispiel aus dem ersten Beitrag auf 4614 bytes (mit -Os)
kommt, liegt die Variante mit der neuen Pinklasse bei 5514 bytes und
wird hässlicher in der Anwendung :(
Bei -O3 sieht das Bild ganz anders aus. Das Paket aus dem ersten Post
kommt dann auf 8402 bytes, während es mit der neuen Pinklasse 6814 bytes
sind.
Ich muss die lss-Dateien noch genauer untersuchen, aber derzeit tendiere
ich dazu, mit der Variante aus dem ersten Beitrag und -Os weiter zu
machen.
Reinhard M. schrieb:> //Edith:> der letzte Absatz war Kwatsch mit Source :(> Da waren die Finger schneller, als das Hirn. Ich schmeiß das deshalb> nicht raus, weil ich die SystemTicker-Klasse auch als Handle verwenden> wollte, um auf Daten zugreifen zu können. Wenn jemand ne Handle-Instanz> anlegt, soll er aber nicht den Timer initialisieren. Deshalb die> Singleton-Instanz. Weiß noch nicht, ob ich das so lasse ...
Es ist kein Singleton, es ist ein Monostate.
Morgen auch =)
Reinhard M. schrieb:> [...]> Kennst Du cmake?
Jo. mehr als mir lieb ist... schlimmes teil aber leider gut.
> Die Quelldateien liegen im> Verzeichnis darüber.> [...]
achso. du meinst dateien - ja ist sauberer. hatte halt bisher nur ein
elf und ein hex, da wars mir wurscht ;)
make konzept ist out of scope bei mir.
>> Es fehlt noch ein Konzept aus Benutzer Sicht, eine art API.>> Hm, da gehe ich einfach anders an die Geschichte heran. Ich komme aus> der Ecke Benutzer-Ergonomie und habe mir angewöhnt, als erstes die> Benutzerschnittstelle festzuklopfen. Genauso mache ich es auch beim> Entwurf von Objekten/Klassen - als erstes überlege ich mir das public> interface. Erst danach überlege ich mir, wie ich das umsetzen kann.> Ist vielleicht nicht der leichteste Weg, aber die Akzeptanz ist deutlich> höher.
Hehe ok lustig. Ich komm aus genau der anderen Ecke, hab bisher meistens
libs geschrieben und versuch immer zuerst einen minimalen aber
vollstaendigen unterbau hinzukriegen.
>> Bitte schau genauer hin:>> Es gibt drei ISRs. Zwei davon sind explizit dynamisch. Das hab ich extra>> so eingebaut.>> [...]> Heißt aber nicht, dass ich jetzt jede Menge überflüssige> Funktionsaufrufe akzeptieren würde.
Ja... hm. In produktivcode wuerde ich das wohl auch nicht.
Deckt vielleicht eher meine art ab code zu schreiben:
prototyp -> sauber machen -> optimieren.
da wuerde ich wohl am schluss die sprungtabelle durch ein switch
ersetzen, wenn ich weiss welche pfade es gibt.
>> Meine ganzen "Klassen" zb. haben keinen zustand, und das>> ist echt was wert aus compiler sicht. Es gibt so nur dann branches und>> calls wenn es notwendig ist.>> Hm, den Vorteil gibt es aber nur auf den ersten, flüchtigen Blick. Die> Anwendung braucht Zustände und die müssen irgendwo hingepackt werden.> Ich halte es nicht für benutzerfreundlich, wenn man sich als Anwender> der Klassen noch Gedanken darüber machen muss, welche Zustände die> Objekte der Klassen brauchen, um funktionieren zu können.
Ja ich glaub da haben wir wieder ne andere perspektive.
Ich will einfach nix ueber die anwendung annehmen die der benutzer
schreiben will, seine zustaende muss er sich schon selber ueberlegen.
ich glaub ich will gar nicht moeglichst freundlich sein zum benutzer,
sondern ihm moeglichst maechtige und minimale werkzeuge geben. (siehe
boost)
> So ein Framework ist ein richtiges Stück Arbeit. Wenn ich mir das antue,> dann will ich aus Anwendersicht auch einen Vorteil von haben. Wenn> dagegen sowas rauskommt, wie die Arduino-Programmierschicht, dann lasse> ich lieber von vorn herein die Finger davon.
ich glaub der groesste vorteil ist, dass ich jetzt versteh wie ein avr
funktioniert, da wars mir der aufwand wert =P
und ich hab halt ne lib die fuer mich passt.
(und ja... motivation die zu schreiben war das arduino zeug. hab ich
gesehen und mir gedacht "oh gott das kann man doch nicht so machen")
> Ob die Klassen jetzt schick aussehen (Deine tun das, ganz ohne Frage!)> ist mir völlig schnuppe. Der Teil der Projekte soll doch möglichst ohne> Anpassung von Projekt zu Projekt kopiert werden. Also ist dort doch der> richtige Platz für schmutzige Hacks, um nachher sauberen Anwendungscode> zu erreichen. Gut, so ist zumindest meine Denke ...> ... und wenn ich zurück denke, an die Fremd-Bibliotheken, die mir im> Laufe der Zeit ans Herz gewachsen sind, dann haben die genau die gleiche> Philosophie beherzigt :)
hehe, wieder unsere philosophien. wenn ich dreckige libs schreib krieg
ich aerger und muss sie nochmal schreiben ;)
>> Wenn du genau hinschaust wird dir auffallen dass ALLE dateien nichts>> ueber den konkreten mikrocontroller wissen.>> Ich habe es gesehen, frage mich aber: ist das für den Anwender eines> Frameworks wirklich wichtig?
naja noe, solangs funktioniert. aber der anwender bin ja ich, und wenn
ich mir nen anderen avr kauf will ich gerne die lib schnell auf den
erweitern koennen ohne ueberall rumsuchen zu muessen wo denn jetzt
device spezifisches zeugs steht.
so weiss ich halt "ok. neue datei anderer_mcu.hpp, bitmasken rein, wenns
baut, dann gehts auch."
ich glaub ich schau mir mal die ganzen libs die hier hochgeladen wurden
aus benutzersicht an und schau mir was ab, und mach meinen unterbau
weiter im c++14/boost stil ;)
lg und danke fuer die freundliche antwort!
PS:
Das thema mit den interrupts beschaeftigt mich jetzt.
ich schau mal ob ich einen (teilweise compile-zeit) zustandsautomaten
hinkrieg, der sich bedienen laesst wie eine sprungtabelle, aber alle
pfade kennt und am schluss nicht call()en muss, sondern inlinen kann.
sowas wuestes in die richtung hatte ich schonmal irgendwo, muss also
gehn :D
Moin moin,
> make konzept ist out of scope bei mir.
Genauso sehe ich das auch. Habe auch keine Lust, meine Zeit mit make und
Co zu vergeuden. Deshalb war ich total glücklich, als ich das Tuhl fand,
mit dem man die Makedatei erzeugen konnte.
Wenn ich mich recht entsinne, war das Teil von Jörg Wunsch
Naja - ich habe das nach meinen Vorstellungen angepasst und seither wird
es ohne groß Nachzudenken von Projekt zu Projekt kopiert.
> Hehe ok lustig. Ich komm aus genau der anderen Ecke, hab bisher meistens> libs geschrieben und versuch immer zuerst einen minimalen aber> vollstaendigen unterbau hinzukriegen.
Das funktioniert, solange man Einzelkämpfer ist. Wenn ein paar Dutzend
Entwickler zusammen arbeiten müssen, dann ist es notwendig, zuerst die
Schnittstellen fest zu klopfen ;)
>>> Meine ganzen "Klassen" zb. haben keinen zustand, und das>>> ist echt was wert aus compiler sicht. Es gibt so nur dann branches und>>> calls wenn es notwendig ist.>>>> Hm, den Vorteil gibt es aber nur auf den ersten, flüchtigen Blick. Die>> Anwendung braucht Zustände und die müssen irgendwo hingepackt werden.>> Ich halte es nicht für benutzerfreundlich, wenn man sich als Anwender>> der Klassen noch Gedanken darüber machen muss, welche Zustände die>> Objekte der Klassen brauchen, um funktionieren zu können.> Ja ich glaub da haben wir wieder ne andere perspektive.> Ich will einfach nix ueber die anwendung annehmen die der benutzer> schreiben will, seine zustaende muss er sich schon selber ueberlegen.
Lach - das ist der Unterschied zwischen weißen und schwarzen Werkzeugen
(neudeutsch: whitebox <> blackbox). Ich bin ein Freund von Blackbox :)
... und so verstehe ich auch die Datenkapselung in OO
Die meisten template-Bibliotheken, die ich bislang gesehen habe, sind
dagegen whiteboxes, d.h. der Anwender kann erst dann eigenen Code
schreiben, wenn er die ganze Bibliothek kennt und verstanden hat.
Das will ich als Anwender aber garnicht.
Mir reicht es, die öffentliche Schnittstelle zu lernen und anzuwenden.
Es ist ein großer Unterschied, ob man das Framework selbst entwickelt
hat (und damit auswendig kennt), oder ob man als Frischling an ein
Framework kommt und sich erst mit den Gedankengängen eines anderen
auseinander setzen muss. Im letzteren Falle kommt man mit blackboxes
schneller zum Ziel.
Wenn das Framework aber nicht ausreicht, die Anforderungen umzusetzen,
dann ist es besser, wenn das Framework eine whitebox ist und man weiß,
wo man Hand anlegen muss ...
> hehe, wieder unsere philosophien. wenn ich dreckige libs schreib krieg> ich aerger und muss sie nochmal schreiben ;)
Schon klar. Ich denke, Du hast mich verstanden. Manchmal muss man
Optimierungen vornehmen, die nicht schön aussehen (inline asm oder auch
die ISR-Funktionen in C++). Die habe ich eben lieber in der Das thema
mit den interrupts beschaeftigt mich jetzt.
ich schau mal ob ich einen (teilweise compile-zeit) zustandsautomaten
hinkriegBibliothek, als im Anwendungscode.
>>> Wenn du genau hinschaust wird dir auffallen dass ALLE dateien nichts>>> ueber den konkreten mikrocontroller wissen.>>>> Ich habe es gesehen, frage mich aber: ist das für den Anwender eines>> Frameworks wirklich wichtig?>>naja noe, solangs funktioniert. aber der anwender bin ja ich
Klar - mit der Einstellung ist es natürlich völlig wurscht, was und wie
Du programmierst. Wenn Du allerdings ne Bibliothek schreibst, die auch
andere anwenden können sollen, dann wird es schon wichtig, wieviel man
lernen muss, um loslegen zu können und wie stark man sich mit der
Bibliothek auseinander setzen muss.
> ich glaub ich schau mir mal die ganzen libs die hier hochgeladen wurden> aus benutzersicht an und schau mir was ab, und mach meinen unterbau> weiter im c++14/boost stil ;)
Hm, wenn wir unsere beiden Philosophien und Codeteile vereinigen
könnten, hätte das bestimmt Potential :)
> Das thema mit den interrupts beschaeftigt mich jetzt.> ich schau mal ob ich einen (teilweise compile-zeit) zustandsautomaten> hinkrieg
Hm, ein Zustandsautomat lässt sich locker ohne Interrupts erstellen ;)
@Sheeva Plug
> Vor einiger Zeit habe ich mir mal Gedanken darüber gemacht, wie man> Register und Pins etwas eleganter in C++ ausdrücken kann.
Gerade hatte ich einen Moment Zeit um mir Deine Beispiele anzuschauen
...
Sehr beeindruckend!
Mir gefällt auch, dass Du den Preprozessor verwendest, um die Anzahl der
Argumente zu reduzieren!
Was mir noch nicht ganz einleuchtet:
In der Registerklasse verwendest Du die gleichen Zeigeranweisungen wie
ich, aber bei Dir wird kein Doppelregister mit dem Zeigerwert geladen.
Was habe ich übergesehen?
und wieso verbrauchen die Members der Pin-Klasse keinen Speicher?
Ich schmeiß mich wech :D
jetzt habe ich eine Variante mit der Pin/Register-Variante von Sheeva
Plug erstellt und übersetzt ...
Dateigröße ändert sich von 4550 bytes program + 189 bytes data
auf 4584 bytes program + 165 bytes data
also 10 bytes mehr :D
LOL - also ich würde sagen, Meister Buchegg hat eine Super Vorlage
geliefert :)
Falls es jemand interessiert: ich habe die Module noch mit
Include-Brokern versehen, sodass man Module über #defines (in Base.h)
aktivieren, bzw. deaktivieren kann, ohne die Makedatei anpassen zu
müssen.
Makedatei liegt im Verzeichnis "nix", also einfach dort reingehen und
make aufrufen.
Vielleicht habt Ihr ja mal Lust, die verschiedenen C++-Varianten Bib zu
vergleichen. Jeder scheint ja so seine eigene kleine Variante zu haben.
Dazu braucht es eine Anforderung, bspw.
1) ein LED alle 1s blinken lassen
2) 8 LED alle 1s blinken lassen
3) USART zum Host mit Echo des Zeichens
4) USART1 vom Host und USART2 zum Host mit Echo
...
Dann könnte man objektiv Code-Size und subjektiv Stil vergleichen ...
Moin Wilhelm,
hast Du Dir auch nen compiler selber geschnitzt?
Habe es gerade probiert, aber mein Linux scheint zu alt zu sein :O
> Jeder scheint ja so seine eigene kleine Variante zu haben.
Naja - es soll ja nicht nur übersetzen, sondern auch funktionieren. Das
bedeutet aber, dass man auch die entsprechende Hardware haben muss. Weiß
nicht, ob man sich da so adhoc auf einen gemeinsamen Nenner einigen
kann.
Reinhard M. schrieb:> Moin Wilhelm,>> hast Du Dir auch nen compiler selber geschnitzt?> Habe es gerade probiert, aber mein Linux scheint zu alt zu sein :O>>> Jeder scheint ja so seine eigene kleine Variante zu haben.>> Naja - es soll ja nicht nur übersetzen, sondern auch funktionieren. Das> bedeutet aber, dass man auch die entsprechende Hardware haben muss. Weiß> nicht, ob man sich da so adhoc auf einen gemeinsamen Nenner einigen> kann.
GCC gibbet als Source und eine Schnitzanleitung hat google auch.
Ok, für Linux ;-)
Als Unix-Mensch und jahrzehntelanger Linux-er verwende ich Arch-Linux.
Da hat man alles immer taufrisch ... Eigentlich schade, dass es clang++
nicht mir nem AVR-Backend gibt.
Ein Testboard zum Aufstecken auf ein Breadboard mit nem 28-DIP oder
40-DIP AVR hat doch bestimmt jeder rumliegen. Und ein paar LEDs und
USB/Usart-Kabel werden wohl auch in irgendeiner Schublade liegen.
Schaun mer mal ...
Moin moin,
> GCC gibbet als Source und eine Schnitzanleitung hat google auch.
Ja, ok. Ich wusste nicht, dass der avr-gcc "nur" eine spezielle
Übersetzung des gcc ist.
Bei debian sind die Versionsunterschiede zwischen native und avr so
gigantisch, dass ich dachte, es sind unterschiedliche Saucen :O
Ok, inzwischen läuft der xte Übersetzungsversuch ...
Vermutlich müsste ich die AVR-libc auch neu bauen?
Mal schauen. Wochenende steht ja vor der Tür ;)
> Ein Testboard zum Aufstecken auf ein Breadboard mit nem 28-DIP oder> 40-DIP AVR hat doch bestimmt jeder rumliegen. Und ein paar LEDs und> USB/Usart-Kabel werden wohl auch in irgendeiner Schublade liegen.
Hm, was hättest Du noch gerne im Testprogram?
Meines hat ein LCD mit Software-SPI, 4 Taster, ADC und Buzzer ...
... dazu noch einen (kleinen) Zustandsautomaten, zwei Ausgabepins ...
Wenn Du was anderes testen möchtest, dann schreib doch mal konkret auf,
was rein soll.
>> Hm, ist der assembler separat?
Ja. Gehört zu den Binutils. Das mindeste was brauchst is Binutils +
GCC + AVR-LibC.
> -I/usr/lib/avr/include
Lass den Köse weg. Entweger es funktioniert ohne, oder deine Toolchain
ist kaputt / falsch konfiguriert / unvollständig...
> as
Ist das der Host as? gas für x86 oder ne andere Host-Plattform wirk
kaum -mmcu=avr5 kennen ;-)
Der verwendete as sollte dem entsprechenden config.log zu entnehmen
sein.
Nabend.
So ich hatte ja das Thema mit der fehlenden api ;)
hab jetzt mal einen ansatz angefangen und finds schoener.
das beispiel weiter oben mit dem knopf und der led die auf dem
invertierten pegel laeuft:
1
#include"mc.hpp"
2
3
intmain(){
4
usingnamespacemc;
5
6
autoled=pin<mcu::pin::pc3>::init(output);
7
autocheck=pin<mcu::pin::pd2>::init(input);
8
9
while(true){
10
led=!check;
11
}
12
}
overhead: 0 byte, es faellt der gleiche assembler wie bei plain c raus.
(pin belegung ist anders, aber die ist auf meinem testboard halt so und
ich steck das jetzt nicht um.)
haette auch mal spass dran ein paar gleiche faelle zu vergleichen und
verschiedene ansaetze daran zu sehen :)
(Wie Wilhelm M. schreibt)
>> Hm, ist der assembler separat? Muss ich den auch noch übersetzen?
Ich mach das immer danach:
http://www.nongnu.org/avr-libc/user-manual/install_tools.html
erst BinUtils, dann GCC, dann AvrLibc.
Danach spiele ich etwas mit Symlinks, da ich mehrere Avr-Gcc Versionen
parallel installiert hab. Eine setze ich dann als Default.
Wobei ich das meist auf Arbeit mache. Da muß ich zwar Windows, aber
nebenher läuft immer eine virtuelle Kiste mit Linux. Da ist das Warten
nicht so öd.
Johann L. schrieb:
> Ja. Gehört zu den Binutils. Das mindeste was brauchst is Binutils +> GCC + AVR-LibC.
Danke für den Hinweis. Wär ja auch zu einfach gewesen :O
David Uebler schrieb:
> hab jetzt mal einen ansatz angefangen und finds schoener.> das beispiel weiter oben mit dem knopf und der led die auf dem> invertierten pegel laeuft
Das sieht schon besser aus. Allerdings solltest Du für einen Taster auch
eine Entprellung machen. Also Interrupt und main-Schleife.
Ich habe bei meinen Beispielen gesehen, dass so ein 2-Zeilen Beispiel
ein völlig anderes Bild suggeriert, als nachher beim kompletten
Testprogramm rauskommt.
Deshalb habe ich eben eine etwas komplexere Beispielanwendung
beigepackt.
> (pin belegung ist anders, aber die ist auf meinem testboard halt so und> ich steck das jetzt nicht um.)
Na, die sollte doch jeder an seine HW anpassen können, ohne dass sich
großartig was an der Firmwaregröße ändert. Ist also kein Aufheben wert
;)
Carl Drexler schrieb:
> Ich mach das immer danach:> http://www.nongnu.org/avr-libc/user-manual/install...> erst BinUtils, dann GCC, dann AvrLibc.
Danke für die Tips und Schande über mein Haupt. Das Handbuch ist das
meistgenutzte bei mir, aber die Seite habe ich noch nie gesehen :(
Also denne: in die Hände spucken und los geht's ;)
neuer Pic-Freund schrieb:
> Cooler Test
Yepp - jetzt hatter mich auch :(
Habe probiert, Support für long-long wegzulassen, genauso wie die
goldene Option ...
... macht keinen Unterschied. Es kracht immer :(
Kleiner Tipp grundsätzlich zum Entwickeln: mach die ne VM (bspw. mit
VirtualBox) mit ArchLinux. Das ist eine rolling distribution und immer
sehr aktuell, und auch v.a. stabil. Etwas Linux-KnowHow ist aber
notwendig... Doch gibt es ganz hervorragende Anleitungen für Anfänger.
Wilhelm M. schrieb:
> Kleiner Tipp grundsätzlich zum Entwickeln
??? - das verstehe ich jetzt nicht. Meinst Du zum Compilerbau, oder zur
µC-Entwicklung?
fremde Quelltext-Pakete baue ich grundsätzlich in /usr/local
Das ist bei mir eine eigene Platte, die jederzeit schnell entfernt und
platt gemacht werden kann :)
für die µC-Entwicklung habe ich auch eigene Umgebungen (anderer Benutzer
;) )
virtualbox verwende ich für Teile, mit denen ich mein System nicht
verunreinigen will (die avr-Geschichten zählen für mich nicht dazu).
Naja - aber wenn der Compiler crasht, dann ist mir auch klar, warum
avr-gcc dem gcc bei debian versionsmäßig hinterher hinkt.
> mit ArchLinux. Das ist eine rolling distribution und immer sehr aktuell
Mein Linux ist debian. Mir ist Sicherheit und Stabilität wichtiger, als
akuellste Pakete zu haben. Habe zwischendurch immer mal wieder andere
Distris ausprobiert, bislang fand ich keine, die mit debian mithalten
oder sie gar ersetzen könnte. Das Thema ist für mich durch :)
Ich dachte schon, Du wolltest den avr-gcc unter M$ erstellen, was aber
mit cygwin auch geht. Es gibt ja Leute, die mit M$ Software entwickeln
;-)
Ah Debian! Davon bin ich schon lange weg, weil ich die vielen Vorteile
(auch absolute Stabilität und die sehr große Community) von ArchLinux
sehr genieße!
Nun, dann musst Du halt das richtige
./configure && make && make install
machen ;-)
> Ich dachte schon, Du wolltest den avr-gcc unter M$ erstellen
Lach - M$ gibt es bei mir nur in einer VM ...
z.B. für Atmel Studio
Das starte ich aber nur, wenn es auf die exakten Prozessorschritte
ankommt ;)
Übrigens:
wenn man gcc aus svn-trunk übersetzt, dann löppt alles wie gewünscht.
Die Ausgabe von avr-size der neuen binutils gefällt mir allerdings
überhaupt nicht. Da musste ich doch glatt das alte avr-size wieder
reanimieren ;)
Bin also mit dem neuen Üersetzer wieder arbeitsfähig. Ging doch
schneller, als erwartet.
Reinhard M. schrieb:> @Sheeva Plug> Sehr beeindruckend!> Mir gefällt auch, dass Du den Preprozessor verwendest, um die Anzahl der> Argumente zu reduzieren!
Danke. ;-)
> Was mir noch nicht ganz einleuchtet:> In der Registerklasse verwendest Du die gleichen Zeigeranweisungen wie> ich, aber bei Dir wird kein Doppelregister mit dem Zeigerwert geladen.> Was habe ich übergesehen?
Mir ist nicht ganz klar, was Du mit "Doppelregister" meinst?
> und wieso verbrauchen die Members der Pin-Klasse keinen Speicher?
Bei mir optimiert der Compiler (g++ 5.4.0, zuvor 4.8.1) das weg.
Johann L. schrieb:> Ist das der Host as?
Würde ich auch vermuten.
Der Dreh ist halt, dass alle Komponenten der Toolchain mit dem
gleichen --prefix gebaut sein müssen. Wenn man jetzt versucht,
die avr-binutils aus der Distribution zu nehmen, dann geht das
prinzipiell, aber man müsste deren --prefix auch für Compiler und
avr-libc benutzen.
Sinnvoller ist es daher, immer alle drei Komponenten neu zu bauen.
Wenn man kein --prefix angibt, landen die („Endkunden“-)Binaries unter
/usr/local/bin.
Reinhard M. schrieb:> Ich schmeiß mich wech :D
Ach, wirklich? }:D
> jetzt habe ich eine Variante mit der Pin/Register-Variante von Sheeva> Plug erstellt und übersetzt ...
Bitte verzeih', aber in Deinem Zipfile kann ich meinen Code gar nicht
finden. Stattdessen sehe ich eine Pin-Klasse, die nur eine entfernte
Ähnlichkeit mit der meinen hat. Mein Reg-Template und die
Register{8,16}-Klassen hast Du vorsichtshalber gleich ganz weggelassen,
dabei könnte man sie in Deinem Projekt ganz wunderbar zur Abstraktion
der anderen Register (TCCRx, OCRx, ADCSRx, ... you name it) benutzen.
Aber naja -- zurück zu meinem Code, bzw. zu dessen Unterschieden zu
Deinem.
Meine main()-Funktion aus "ex0" wird vom Compiler letztlich zu folgendem
gemacht:
Du siehst: die set{Low,High}()- und die isHigh()-Methoden werden am Ende
zu einem einzelnen Maschinenbefehl optimiert. Damit wird klar: Deine
"cli()"- und "sei()"-Aufrufe in den Methoden sind komplett überflüssig,
denn eine einzelne Instruktion kann ohnehin nicht unterbrochen werden.
Ebenso überflüssig ist Dein "inline"n meiner Methoden, denn für einzelne
Instruktionen spendiert der Compiler sicher keinen Funktionsaufruf.
Bei alten AVRs könnte das sei/cbi bei toggle() ansonsten vielleicht noch
sinnvoll sein, da sind das mehrere Instruktionen. Aber bei den modernen
AVRs kann man einen Pin toggle()n, indem man einfach eine "1" auf das
Pin-Register schreibt, was wieder nur eine Instruktion ist. Das wird bei
mir daher über CAN_TOGGLE_PIN gesteuert.
Andererseits gefällt mir Dein Handling von "PortDesc Port[A-D]" so gut,
daß ich überlege, es zu übernehmen. Das macht die Sache dann noch etwas
besser lesbar als mein PINDEF()-Makro.
> Dateigröße ändert sich von 4550 bytes program + 189 bytes data> auf 4584 bytes program + 165 bytes data> also 10 bytes mehr :D
Na, das ist eine Differenz von gerade einmal 2,1 Promille. Dafür, daß Du
meinen Code, nunja, leider "kaputtgemacht" hast, ist das noch ziemlich
überschaubar, finde ich. Probier' mal meinen Originalcode, da sieht die
Sache sicher anders aus.
Das tut sie bei mir aber ohnehin: der Code aus Deinem Zipfile kompiliert
bei mir zu 4500 bytes program + 189 bytes data => 4689 bytes, also 50
Bytes weniger (avr-g++ 4.9.1 unter Ubuntu 16.04.1 LTS). Wenn ich dazu
-flto einschalte, komme ich auf nurmehr 4144 bytes program + 189 bytes
data => 4333 Bytes gesamt: 406 Bytes weniger!
Und wenn ich Deine kontraproduktiven "Optimierungen" herausnehme und das
Ganze neu übersetze, komme ich auf: 4128 bytes program + 189 bytes data
=> 4317 bytes insgesamt, also 8,9 Prozent (Prozent, nicht Promille)
weniger als bei Deinem Elaborat. Das ist allerdings deutlich, finde ich.
> LOL - also ich würde sagen, Meister Buchegg hat eine Super Vorlage> geliefert :)
Das hat ja niemand bestritten, aber erst macht Du meinen Code kaputt,
und dann machst Du Dich über das Ergebnis lustig. Ist doch prima: so
haben wir am Ende beide was zu Lachen gehabt. ;-)
Sheeva Plug schrieb:
> Mir ist nicht ganz klar, was Du mit "Doppelregister" meinst?
Na, vor der LD-Anweisung werden doch zwei Register mit der Adresse der
Speicherstelle geladen, also 3 Anweisungen für eine Zeigerzuweisung.
>> und wieso verbrauchen die Members der Pin-Klasse keinen Speicher?>> Bei mir optimiert der Compiler (g++ 5.4.0, zuvor 4.8.1) das weg.
Hm, könntest Du bitte mal Deine Compiler-Optionen veröffentlichen?
Bei mir wird zwischen 4.8 und 7.0 nichts anders optimiert :(
Jörg Wunsch schrieb:
> Sinnvoller ist es daher, immer alle drei Komponenten neu zu bauen.> Wenn man kein --prefix angibt, landen die („Endkunden“-)Binaries unter> /usr/local/bin.
Genauso habe ich es dann auch gemacht. Musste mich nur erst auch an die
richtige Reihenfolge gewöhnen. Beim gcc 6.2 kommt der oben zitierte
Fehler, bei der Version aus trunk läuft alles gut.
Allerdings sehe ich keine großen Veränderungen im Output. Wenn ich meine
Quellen mit dem neuen compiler übersetze, kommt aufs Byte die gleiche
Firmware heraus.
Sheeva Plug schrieb:
>> jetzt habe ich eine Variante mit der Pin/Register-Variante von Sheeva>> Plug erstellt und übersetzt ...>> Bitte verzeih', aber in Deinem Zipfile kann ich meinen Code gar nicht> finden.
Das ist korrekt, da ich den ja garnicht hochgeladen habe.
Ich habe inzwischen auch das Buch aus dem Tip überflogen. Der Autor
scheint realistisch zu sein. Er gehört nicht zu den template-Fanboys,
sondern schreibt, dass templates die Codegröße positiv beeinflussen
KANN aber auch zu einer Codegrößen-Explosion führen kann. Mann müsste
also jeweils eine template-Variante mit einer Non-Template-Variante
vergleichen. Also im Prinzip das, was ich auch gemacht habe.
Wenn man völlig state-lose Bibliotheken baut, dann kann man noch
Beispiele erzeugen, bei denen die Codegröße überschaubar bleibt.
Spätestens dann, wenn man aber unterschiedliche Zustände braucht
und/oder nutzen will, bezahlt man für den Code. Meiner Ansicht nach
zeigt sich erst dann, was eine Bibliothek wert ist.
> Andererseits gefällt mir Dein Handling von "PortDesc Port[A-D]" so gut,
Das ist nicht "mein" Handling. Das habe ich mir von Meister Buchegg
abgeschaut und nur eine Referenz aus der Instanzvariablen gemacht.
> aber erst macht Du meinen Code kaputt, und dann machst Du Dich über das> Ergebnis lustig.
Moment! Da hast Du mein Lachen falsch interpretiert!
Ich habe mich weder über Dich, noch über Deine Bibliothek lustig
gemacht, sondern über die 10 byte Unterschied.
Wenn ich zwei Varanten übersetzt habe, setz ich mich hin und mach eine
Kosten-/Nutzen-Rechnung. Und spätestens dann sind alle template-Ansätze,
die ich bislang fand, vom Tisch. Auch die Beispielanwendung aus dem Buch
ist viel zu kompliziert für den (Hobby-)Einsatz. Im Buch wird auch nicht
alles erklärt, was dann in der Beispiel-Anwendung umgesetzt wird. Bleibt
also vieles in der Grauzone.
Wer ohne das Buch die Bibliothek für eigene Projekte verwenden will, hat
einen sehr steinigen Weg vor sich. Eine gute Bibliothek sieht für mich
anders aus.
Somit sind wir wieder am Anfang, d.h. es muss jeder selbst für sich
heraus finden, was wie teuer ist und was ihm jetzt was wert ist.
Hallo zusammen,
hier eine neue Version meiner Klassenbibliothek.
Ich habe die Klassen etwas umgebaut, um sie flexibler verwenden zu
können.
Dann habe ich auch Unterstützung für weitere Prozessoren zugefügt.
Derzeit übersetzen die Klassen für - mega88, mega16, mega168, mega328
, mega640, mega1280, mega1281
, mega2560 and mega2561
wobei es ab mega1280 aktuell noch Linker-Probleme gibt. Zumindest mit
gcc-7.0 aus dem trunk.
OK, das Linkerproblem scheint behoben zu sein.
Deshalb hier eine neue Version mit korrigiertem Makefile.
Damit werden jetzt alle unterstützten Professoren übersetzt ;)
Reinhard M. schrieb:> Sheeva Plug schrieb:>> Bei mir optimiert der Compiler (g++ 5.4.0, zuvor 4.8.1) das weg.>> Hm, könntest Du bitte mal Deine Compiler-Optionen veröffentlichen?
-Os -std=c++11 -Wall -Wexec -Wpedantic
> Ich habe inzwischen auch das Buch aus dem Tip überflogen. Der Autor> scheint realistisch zu sein. Er gehört nicht zu den template-Fanboys,> sondern schreibt, dass templates die Codegröße positiv beeinflussen> KANN aber auch zu einer Codegrößen-Explosion führen kann. Mann müsste> also jeweils eine template-Variante mit einer Non-Template-Variante> vergleichen. Also im Prinzip das, was ich auch gemacht habe.
Naja, wenn man weiß, wie Templates funktionieren, ist das gar nicht so
magisch. Tatsächlich verwendet mein Code Templates, um die Register zu
abstrahieren. Das erzeugt jeweils eine Register8-Klasse für 8-Bit- und
eine Register16-Klasse für 16-Bit-Register -- wenn sie gebraucht werden,
und eben nur dann. Ich halte im Übrigen auch nichts davon, Pinnummern,
Registeradressen etc. als Template-Parameter zu übergeben -- Templates
sind nicht als alternativer Mechanismus zur Parameterübergabe gedacht.
> Wenn man völlig state-lose Bibliotheken baut, dann kann man noch> Beispiele erzeugen, bei denen die Codegröße überschaubar bleibt.
Ja, natürlich. Und wenn ich ein Register abstrahieren will, warum sollte
ich das nicht stateless machen? Der Status steht ja ohnehin im Register.
> Spätestens dann, wenn man aber unterschiedliche Zustände braucht> und/oder nutzen will, bezahlt man für den Code. Meiner Ansicht nach> zeigt sich erst dann, was eine Bibliothek wert ist.> Moment! Da hast Du mein Lachen falsch interpretiert!
Ah, ok. ;-)
> Somit sind wir wieder am Anfang, d.h. es muss jeder selbst für sich> heraus finden, was wie teuer ist und was ihm jetzt was wert ist.
Das ist ja immer so.
Sheeva P. schrieb:> Reinhard M. schrieb:>> Sheeva Plug schrieb:>>> Bei mir optimiert der Compiler (g++ 5.4.0, zuvor 4.8.1) das weg.>>>> Hm, könntest Du bitte mal Deine Compiler-Optionen veröffentlichen?>> -Os -std=c++11 -Wall -Wexec -Wpedantic
da würde ich gleich mal auf c++1z gehen, um die neuesten
Errungenschaften auch benutzen zu können.
> Naja, wenn man weiß, wie Templates funktionieren, ist das gar nicht so> magisch. Tatsächlich verwendet mein Code Templates, um die Register zu> abstrahieren. Das erzeugt jeweils eine Register8-Klasse für 8-Bit- und> eine Register16-Klasse für 16-Bit-Register -- wenn sie gebraucht werden,> und eben nur dann. Ich halte im Übrigen auch nichts davon, Pinnummern,> Registeradressen etc. als Template-Parameter zu übergeben -- Templates> sind nicht als alternativer Mechanismus zur Parameterübergabe gedacht.
Die Entscheidung liegt ja darin, ob man Compiletime-Polymorphie benutzen
kann (oder etwas zur Laufzeit entschieden werden muss). Das kann
natürlich zu einer Vergrößerung des MaschinenCode-Umfangs führen. Was
ich aber meistens als unkritisch empfinde, da oft nicht das flash der
limitierende Faktor ist, sondern das RAM (zumindest mal auf den
kleineren AVRs).
Wichtig ist auch, ob man Funktionen als constexpr gestalten kann.
constexpr ist auch eine wunderbare Neuerung (aber da wiederhole ich
mich) wie auch variadische templates, um z.b. zur Compilezeit
Iterationen auszuführen.