Hans-Georg Lehnard schrieb: > sonst hast du deinen mühsam gespaarten > Speicher schnell, durch eine lib die automatisch dazugelinkt wird, > wieder zunichte gemacht. … wie z.B. bei GoTo ^^ mit "boost::thread". ;-) Einige der "header-only Boost libraries" gibt es ja wohl auch für den AVR, aber natürlich ohne boost::thread. Ich wage mich da mit meinem ATtiny1634 ganz bestimmt nicht ran: http://academic.cleardefinition.com/2012/09/21/using-c-on-the-arduino-a-mainstream-c-standard-library-port-for-the-avr/ Oder hat damit etwa schon mal jemand gute Erfahrungen gemacht? Trotzdem nochmal: Falls jemand einen Vorschlag hat, wie man "class Pin"^^ ohne Pointer machen könnte, das wäre dann noch eleganter.
:
Bearbeitet durch User
Torsten C. schrieb: > Ralf G. schrieb: >> DDRx, PORTx, PINx sind keine Pointer > > Und wozu ist der Stern in "public: volatile uint8_t* ddr;"^^? > > Oder hast Du einen Vorschlag, wie man das ohne Pointer macht? Wenn man hier auch folgendes verwenden kann:
1 | volatile uint8_t*const ddr; |
Sind auch referenzen möglich:
1 | volatile uint8_t& ddr; |
PS: In dem Bespiel, welches ich ganz weit oben Postete, habe ich dafür Referenzen verwendet.
:
Bearbeitet durch User
kopfkratz Also nochmal langsam und zum mitmeisseln (oder wird das mit ß geschrieben ?) ... Man muß die Abstraktionsstufen im Auge behalten. Entweder man definert eine vorgegebene Struktur die immer die gleichen Variablen nutzt aber für jeden µC entsprechend angepaßt ist (so wie ich das schon erwähnt habe). Oder man geht den Weg der Vererbung und muß dann für jeden µC die passende Basisklasse schreiben ebenfalls wie im obigen Fall. Der Weg über Templates ist der meiner Meinung nach sinnvollste und flexibelste, denn dann spart man sich die Vererbung bzw. Einbindung verschiedener Definitionen. Damit kommt man aber von einer Basisdefinition des µCs auch nicht herum. Von der Compilerseite könnte man das auch im makefile erledigen bzw. beim linken.
Torsten C. schrieb: > Falls jemand einen Vorschlag hat, wie man "class > Pin"^^ ohne Pointer machen könnte, das wäre dann noch eleganter. ??? Na so: Beitrag "Re: C++ auf einem MC, wie geht das?" Die Registeradressen würde man allerdings über die Port-Adresse ermitteln lassen. Den Rest macht der Compiler. Der kann das. Vorteil: - geht für alle AVRs einheitlich. - es sind automatisch auch die mit im Boot, wo die Register nicht im IO-Bereich liegen. Ralf G. schrieb: > Ich habe > allerdings noch keinen Einfall, wie man solche Objekte universell in > andere Objekte einbinden kann (ohne diese <>-Klammerlisten). Da wird man wohl nicht drum herumkommen... Daniel A. schrieb: > Wenn man hier auch folgendes verwenden kann: ^^ Das alles sind Beispiele, wie sie schon seit Jahren immer einmal, hier im µC-net vorgeschlagen/ diskutiert werden. Ob Pointer, Referenzen, 'const' oder nicht 'const'... Ist egal. Kann man so machen. Macht man auch so! Wenn man weiß und das toleriert, dass da nur direkte Zugriffe draus werden, wenn der Compiler den kompletten Code sehen kann.
Hallo Torsten, Torsten C. schrieb: > Nun habe ich nur noch ein Problem mit Instanzen statt Templates: Kann > man irgendwie verhindern, dass ein Pin doppelt instanziiert wird? Ich > kenne dafür nur Singletons. Sollte man versuchen, Singletons umzusetzen? Ach, Torsten... Jedes Mal, wenn Du etwas idiotensicher machen willst, dann kommt die Natur und erfindet bessere Idioten. Kurz gesagt: Man kann nicht verhindern, daß Idioten idiotische Sachen tun. Das ist auch nicht die Aufgabe einer Bibliothek. Was dabei heraus kommt, wenn Du es versuchst, siehst Du an den Arduino-Libraries: funktioniert zwar, ist aber ein riesiger Source- und Codebloat. Und trotzdem gibt es genug Idioten, die idiotische Sachen damit machen -- schau mal in dieses Forum! ;-) Mit dem Anspruch, jeden noch so bescheuerten Fehler mit Compiler- und Sprachmitteln abzufangen, stehst Du Dir daher einerseits selbst im Weg, andererseits verhinderst Du wahrscheinlich exotische Dinge, die wir uns vielleicht nur nicht vorstellen können, die aber trotzdem sinnvoll sind. Es ist nicht Deine Aufgabe, die Anwender Deiner Bibliothek zu bevormunden, und darauf läuft die ganze Sache letztendlich hinaus (ja, ich weiß, das ist nicht Deine Intention, aber "gut gemeint" ist bekanntlich die kleine Schwester von "schlecht gemacht"). Wenn Anwender idiotische Sachen machen wollen, ist das ihr gutes Recht. Etliche gute Ideen und Produkte sind aus etwas entstanden, das andere zuvor als sinnlos, idiotisch abgetan haben. Ich schlage daher vor, daß wir diesen Quatsch endlich lassen und uns darum kümmern, ordentlichen, funktionierenden Code zu schreiben -- und zwar auch auf die Gefahr hin, daß Idioten nun einmal idiotische Sachen damit tun. Liebe Grüße, Karl
Hallo Torsten, Torsten C. schrieb: >
1 | volatile uint8_t* ddr; |
2 | > volatile uint8_t* port; |
3 | > volatile uint8_t* pin; |
> > Warum eigentlich drei getrennte Pointer? Einen einzigen fände ich übersichtlicher. Weil die Hardware der AVRs das nun einmal so abbildet. Ansonsten gibt es in dem von mir geposteten Code ein sehr übersichtliches Makro, das trotzdem für alle aktuellen und zukünftigen AVRs korrekt funktioniert. Liebe Grüße, Karl
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Ich habe auch immer gedacht ich kann C++ und was soll das alles mit dem > C++11 das brauch ich doch nie aber genau hier für die Embedded > Programmierung kann man es sehr gut gebrauchen. Der Nachteil ist, es ist > nicht leicht verständlich und für Anfänger in C++ völlig ungeeignet. Die > Compiler Fehlermeldungen sind alles andere als verständlich und debuggen > im single-step lässt sich das erst recht nicht. Für PC-Programme nutze ich mittlerweile statt des g++ den LLVM-Compiler mit dem clang++-Frontend. Das wird genauso aufgerufen wie der g++ (mit denselben Optionen und Schaltern), gibt aber sehr viel bessere Fehlermeldungen aus und erzeugt in oft auch besseren (iSv. kleineren oder schnelleren) Code. HTH, Karl
Hallo Torsten, Torsten C. schrieb: > Ich meine, die Pointer müssen konstant sein, damit der Compiler da > wirklich zuverlässig z.B. "sbi 0x17, 3" draus macht und nicht nur > zufällig je nach Compiler-Optimizing-Level. Guckstu avr/io.h: DDRD, PIND, PORTB und PB4 sind Konstanten! HTH, Karl
Hi Torsten, Torsten C. schrieb: > Ralf G. schrieb: >> DDRx, PORTx, PINx sind keine Pointer > > Und wozu ist der Stern in "public: volatile uint8_t* ddr;"^^? Rein technisch betrachtet ist ein Pointer (Zeiger) etwas, das auf einen bestimmten Speicherbereich zeigt -- meistens ist das eine Variable, hier aber eben nicht! Dieser Zeiger hier zeigt auf einen festen Speicherbereich, nämlich ein Register, bei dem der Inhalt des Speicherbereichs volatil, der Zeiger auf den Speicherbereich hingegen konstant ist. Insofern ist das aus Sicht der Programmiersprache ein konstanter Pointer auf etwas Verändliches, und gleichzeitig aus Sicht der Hardware eine Registeradresse. Liebe Grüße, Karl
kopfkratzer schrieb: > *kopfkratz* > Also nochmal langsam und zum mitmeisseln (oder wird das mit ß > geschrieben ?) ... Es geht etwas wirr durcheinander, aber wie soll man das verhindern .. > Man muß die Abstraktionsstufen im Auge behalten. > Entweder man definert eine vorgegebene Struktur die immer die gleichen > Variablen nutzt aber für jeden µC entsprechend angepaßt ist (so wie ich > das schon erwähnt habe). > Oder man geht den Weg der Vererbung und muß dann für jeden µC die > passende Basisklasse schreiben ebenfalls wie im obigen Fall. > Der Weg über Templates ist der meiner Meinung nach sinnvollste und > flexibelste, denn dann spart man sich die Vererbung bzw. Einbindung > verschiedener Definitionen. > Damit kommt man aber von einer Basisdefinition des µCs auch nicht herum. > Von der Compilerseite könnte man das auch im makefile erledigen bzw. > beim linken. Wenn wir das auf den "speziellen" Fall 8Bit,AVR,SFR und Port reduzieren geht es einfach darum ein paar 8-bit Register zusammen zu fassen und sie für jeden Port als konstant definieren. Konstante haben aber die Eigenschaft, das sie sofort initialisiert werden müssen, und deshalb kommen Konstruktoren nicht in Frage. Es sind und bleiben aber immer Konstante die nur verschieden initialisiert werden. Beispiel: const int x = 4711; const int y = 4712; würdest du da auch y von x erben lassen ?
Karl Käfer schrieb: > Hallo Hans-Georg, > Für PC-Programme nutze ich mittlerweile statt des g++ den LLVM-Compiler > mit dem clang++-Frontend. Das wird genauso aufgerufen wie der g++ (mit > denselben Optionen und Schaltern), gibt aber sehr viel bessere > Fehlermeldungen aus und erzeugt in oft auch besseren (iSv. kleineren > oder schnelleren) Code. > Der gibt bei Template Fehlern sehr viel bessere Fehlermeldungen aus ? Das würde mich sehr wundern.
Karl Käfer schrieb: > > Guckstu avr/io.h: DDRD, PIND, PORTB und PB4 sind Konstanten! > Das sind Preprocessor Definitionen und die kümmern sich nicht um Namespaces und kollidieren mit allem möglichen, deshalb in C++ eher ungeeignet. Dafür gibt es const und constexpr. Für #ifdef sind sie in Ordnung aber mehr nicht.
:
Bearbeitet durch User
Hans-Georg Lehnard schrieb: > Konstante haben aber die Eigenschaft, das sie sofort initialisiert > werden müssen Das stimmt, ist ein echter Vorteil (keine ironie) > und deshalb kommen Konstruktoren nicht in Frage Falsch, konstruktoren haben eine Initializer list! Das geht!
Daniel A. schrieb: > Hans-Georg Lehnard schrieb: >> Konstante haben aber die Eigenschaft, das sie sofort initialisiert >> werden müssen > Das stimmt, ist ein echter Vorteil (keine ironie) >> und deshalb kommen Konstruktoren nicht in Frage > Falsch, konstruktoren haben eine Initializer list! Das geht! Hast Recht und mit constexpr Konstruktoren gehts auch.
Hans-Georg Lehnard schrieb: > Hast Recht und mit constexpr Konstruktoren gehts auch. Genau das meinte ich oben mit "Das geht per constexpr wunderbar. Ich hab's gerade mit avr GCC 4.7.2 probiert." Cool, dann sind wir uns einig? Ich frage mich jetzt gerade, ob und ggf. worüber eine weitere Diskussion hier noch Sinn macht. Das ganze ist ja etwas "abstrus", weil sich der OP PeDa ofenbar aus dem Thread zurück gezogen hat, und wir hier ein Stück weit eine "Phantom-Diskussion" führen. Ich habe dabei viel gelernt und habe hoffentlich hier und da auch nützliche Beiträge geleistet. Zum Thema uCpp Frank M. schrieb: > Ausserdem halte ich eine "plattformübergreifende Lösung" für > ein Unterfangen, dass sehr viel Arbeit bedeutet, bei dem das Ziel in so > weiter Ferne liegt, dass - wenn eine Lösung denn irgendwann existieren > sollte - die Hardware dafür dann gar nicht mehr existieren wird. > Kurz: ich halte diesen Anspruch für ein Fass ohne Boden. Das mag - je nach Anspruch - darauf hinaus laufen und eine Fortführung des Themas wäre in diesem Thread wahrscheinlich falsch platziert. Gibt es Einsprüche? ;-)
:
Bearbeitet durch User
TriHexagon schrieb: >> Chris D. schrieb: >>> trivial aussehende Probleme eben nicht trivial sind >> >> ... denn die von Peter D. skizzierten Problemchen SIND u.a. in Asm >> trivial zu lösen. > > Dann zeig doch mal. Mach ich gerne noch wenn Bedarf besteht- obwohl solcherlei Trivialitäten nun wirklich jeder mittelmäßige Asm-Progger hinbekommt. Denn man gewinnt ja fast den Eindruck, daß manche C++ Experten vor dem Herrn scheinbar gar keinen Schimmer mehr davon haben, wie die einfachste Problemlösung für LED0 = KEY0.state; // an solange Taste gedrückt LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke LED2.off = KEY2.press_short; // kurz drücken - aus LED2.on = KEY2.press_long; // lang drücken - an ausschaut. Torsten C. schrieb: > Ich habe dabei viel gelernt und habe hoffentlich hier und da auch > nützliche Beiträge geleistet. Das von Peter D. gesuchte fehlt immer noch: Wie mit C++ eine einfache Lösung obiger Anweisungen gelingt. Torsten C. schrieb: > Das mag - je nach Anspruch - darauf hinaus laufen und eine Fortführung > des Themas wäre in diesem Thread wahrscheinlich falsch platziert. Also Kapitulation auf der ganzen Linie. Wie zu erwarten, sind die hochtrabenden Bemühungen an der Komplexität der Sprache erstickt.
Torsten C. schrieb: > Hans-Georg Lehnard schrieb: >> Hast Recht und mit constexpr Konstruktoren gehts auch. > > Genau das meinte ich oben mit "Das geht per constexpr wunderbar. Ich > hab's gerade mit avr GCC 4.7.2 probiert." > > Cool, dann sind wir uns einig? > Ich hatte damit nie Probleme ;) > Ich frage mich jetzt gerade, ob und ggf. worüber eine weitere Diskussion > hier noch Sinn macht. Das ganze ist ja etwas "abstrus", weil sich der OP > PeDa ofenbar aus dem Thread zurück gezogen hat, und wir hier ein Stück > weit eine "Phantom-Diskussion" führen. > Die Diskussion finde ich weder "Phantom" noch "abstrus" eher etwas offtopic, weil von der ursprünglichen Frage eines Praktikers durch viele theoretische Beiträge nicht viel übrig geblieben ist ... Das ist jetzt nicht abwertetend gemeint. > Ich habe dabei viel gelernt und habe hoffentlich hier und da auch > nützliche Beiträge geleistet. > So sehe ich das auch. > Zum Thema uCpp Frank M. schrieb: >> Ausserdem halte ich eine "plattformübergreifende Lösung" für >> ein Unterfangen, dass sehr viel Arbeit bedeutet, bei dem das Ziel in so >> weiter Ferne liegt, dass - wenn eine Lösung denn irgendwann existieren >> sollte - die Hardware dafür dann gar nicht mehr existieren wird. >> Kurz: ich halte diesen Anspruch für ein Fass ohne Boden. > > Das mag - je nach Anspruch - darauf hinaus laufen und eine Fortführung > des Themas wäre in diesem Thread wahrscheinlich falsch platziert. > Das mag nicht nur das ist garantiert so. > Gibt es Einsprüche? ;-) Gegen die Einstellung einer Lib Entwicklung aus den genannten Gründen gibt es nichts zu sagen. Es wäre ähnlich xpcc geworden, da kann man das auch gleich benutzen wenn man will. Das Thema an sich ist für mich noch nicht erledigt aber da kann ich auch alleine weiter im stillen Kämmerlein werkeln. Den Thread kann man, meiner Meinung nach, aber offen lassen !.
Noch hab ich keine ASM-Lösung schimmern sehen. Soviel zur Kapitulation!
Bastler schrieb: > Noch hab ich keine ASM-Lösung schimmern sehen. Ist auch besser so, es geht hier um etwas anderes als Rechthaberei mit ASM. Macht doch für sowas einen anderen Thread auf!
Das war doch nur die Antwort auf "ihr seit wegen der Sprache immer noch nicht fertig". Wollte sagen "der Einzeller aber auch nicht". Sonst: ich kenne ASM. Viele verschiedene. Und will das deshalb nicht mehr selber machen. So nun warte ich wieder gespannt auf C++?
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Karl Käfer schrieb: >> >> Guckstu avr/io.h: DDRD, PIND, PORTB und PB4 sind Konstanten! >> > Das sind Preprocessor Definitionen Das sind Präprozessor-Makros für Präprozessor-Makros, die dann letzten Endes in Konstanten aufgelöst werden. Das Makro "DDRB" wird in avr/iotn13.h durch "_SFR_IO8(0x17)" ersetzt. "_SFR_IO8(io_addr)" wiederum ist ein Makro für "((io_addr) + __SFR_OFFSET)", und da in diesem Falle io_addr (0x17) und __SFR_OFFSET (je nach AVR-Architektur 0x00 oder 0x20) bekannte Konstanten sind, ist das Ergebnis dieser Addition eine Konstante, die der Compiler zur Compilezeit berechnet -- genau wie wenn Du "int a = 3 + 4" in Deinen C-Code schreibst. Es ist ein wenig verkürzt, zugegeben -- aber im Ergebnis stehen diese genannten Präprozessor-Makros also für Konstanten, die zur Compilezeit bekannt sind, ohne den Compiler explizit darauf hinweisen zu müssen. > und die kümmern sich nicht um Namespaces und kollidieren mit allem > möglichen, deshalb in C++ eher ungeeignet. Dafür gibt es const und > constexpr. Irgendwie wirst Du konstante Registeraddressen halt mit symbolischen und verständlichen Namen versehen müssen. Mir erscheinen Bezeichner wie DDRD und Co. da schon deswegen sinnvoll, weil sie so auch in den Datenblättern der MCUs verwendet werden. Wenn Du eine bessere Lösung kennst, die ebenso wenig kostet und ebenso verständlich ist: sehr gerne, her damit. ;-) Liebe Grüße, Karl
Ausgangspunkt: Beitrag "Re: C++ auf einem MC, wie geht das?" Karl Käfer schrieb: > eine Konstante, die der Compiler zur Compilezeit berechnet Und die berechneten Werte werden in dem Code aus Deinem o.g. Beitrag im ctor der "class Pin" den folgenden Pointern zugewiesen:
1 | volatile uint8_t * const ddr; |
2 | volatile uint8_t * const port; |
3 | volatile uint8_t * const pin; |
Wie man sieht: Die gewohnten Namen sind wiedererkennbar! Ich habe nur ein "const" ergänzt, wegen "ichbinmaldiesmaldas"^^. Ich dachte, das wäre geklärt. Man könnte dem ctor die Makros "DDRB" usw. übergeben oder die Adressen anders berechnen. Bei "anders" muss man - je nach Methode - "constexpr" benutzen, bei enums^^ wäre man sogar typsicher. Beides geht. U. a. Weil #define nicht typsicher ist, sind z.B. in C# mit #define keine Wert-Zuweisungen mehr möglich, sondern nur noch "bedingte compilierung". Wenn Dir das egal ist, nimm Macros.
:
Bearbeitet durch User
Hans-Georg Lehnard schrieb: > Das Thema an sich ist für mich noch nicht erledigt aber da kann ich auch > alleine weiter im stillen Kämmerlein werkeln. Zur Frage des OP fehlen m.E. nur noch ein "Zustandsautomat" und ein "Timer". Ein Timer wäre ja einfach 'ne weitere Klasse, die ohne template<> nach dem gleichen Schema wie "class Pin"^^ aufgesetzt wird. Ralf G. schrieb: > Ralf G. schrieb: >> Ich habe >> allerdings noch keinen Einfall, wie man solche Objekte universell in >> andere Objekte einbinden kann (ohne diese <>-Klammerlisten). > Da wird man wohl nicht drum herumkommen... Das man um "diese <>-Klammerlisten" herum kommt, falls mann will, wurde ja an "class Pin" von Karl Käfer gezeigt, oder sind da noch Fragen offen? Hans-Georg Lehnard schrieb: > weil von der ursprünglichen Frage eines Praktikers durch viele > theoretische Beiträge nicht viel übrig geblieben ist ... Meinst Du z.B. die theoretischen Beiträge um den Zustandsautomaten? Wollen wir uns dazu auch noch auf ein gemeinsames Ergebnis einigen? Ich kann dazu erstmal wenig sagen, weil ich erstmal die einfache Delegates^^-Variante ohne <>-Klammerlisten umgesetzt habe und die Variante mit Templates "Boost_sm.png"^^ erst noch ausprobieren müsste.
:
Bearbeitet durch User
Hallo Karl, wir wollen schon das gleiche .. "sprechende Namen" Beispiel: in avrio.h #define PINA _SFR_IO8(0x19) #define DDRA _SFR_IO8(0x1A) #define PORTA _SFR_IO8(0x1B) #define PINB _SFR_IO8(0x16) #define DDRB _SFR_IO8(0x17) #define PORTB _SFR_IO8(0x18) #define PINC _SFR_IO8(0x13) #define DDRC _SFR_IO8(0x14) #define PORTC _SFR_IO8(0x15) #define PIND _SFR_IO8(0x10) #define DDRD _SFR_IO8(0x11) #define PORTD _SFR_IO8(0x12) usw. Und ich fasse die Port Register einmalig in einer Struktur zusammen und weil es auch Ports mit einem weiteren Register für pull up gibt habe ich 2 Typen definiert struct AvrPortRegsTyp1 { uint8_t in; // PINx input register uint8_t dir; // DDRx input register uint8_t out; // DATAx output register }; struct AvrPortRegsTyp2 { uint8_t in; // PINx input register uint8_t dir; // DDRx direction register uint8_t out; // DATAx output register uint8_t pue; // PUEx pull up enable }; Und definiere dann meine Ports als Konstante . #ifdef _AVR_ATmega16_ constexpr AvrPortRegsTyp1 PORTA = {0x19,0x1A,0x1B}; constexpr AvrPortRegsTyp1 PORTB = {0x16,0x17,0x18}; constexpr AvrPortRegsTyp1 PORTC = {0x13,0x14,0x15}; constexpr AvrPortRegsTyp1 PORTD = {0x10,0x11,0x12}; #endif // _AVR_ATmega16_ #ifdef _AVR_ATtiny1634_ constexpr AvrPortRegsTyp2 PORTA = {0x0F,0x10,0x11,0x12}; constexpr AvrPortRegsTyp2 PORTB = {0x0B,0x0C,0x0D,0x0E}; constexpr AvrPortRegsTyp2 PORTC = {0x07,0x08,0x09,0x0A}; #endif // _AVR_ATtiny1634_ Der Unterschied ist aber mein PORTA definiert den Kompletten Port und nicht nur ein Register und das gefällt mir besser und ich halte es für stimmiger. Das lässt sich auch auf alle anderen "Module" wie USART, TIMER usw. anwenden. Würde ich nun avrio.h davor includen, würden meine Definitionen durch den Preprocessor verändert werden, weil die in avrio.h als Macro definiert sind. Ich kann das auch nicht verhindern indem ich mein Code in einen Namespace packe. Das wollte ich damit sagen, mehr nicht ...
Torsten C. schrieb: > Bei "anders" muss man - je nach Methode - "constexpr" benutzen Auch mit dem Risiko weiterer "vieler theoretischer Beiträge"^^: Mich würde mal interessieren, wie weit man das treiben kann. Vielleicht könnte man in einer constexpr-Funktion sogar eine XML-Datei abfragen, z.B. so ein jinja2-Headerfiletemplate^^. ;-) OK, dann dauert das Compilieren ewig, war doof. Aber es würde zeigen, was alles möglich wäre, wenn man constexpr-Funktionen statt #define benutzt.
:
Bearbeitet durch User
Hans-Georg Lehnard schrieb: > constexpr AvrPortRegsTyp2 PORTC = {0x07,0x08,0x09,0x0A}; > … und das gefällt mir besser und ich halte es für stimmiger. Mir auch / ich auch. Cool, Deine Arrays! :-) Genau so werde ich das in Zukunft machen! Eventuell als Übergang erstmal, indem ich die Start-Adressen aus avrio.h hole. Hauptsache der Editor kennt die nicht und die ollen Macros werden nicht dauernd vorgeschlagen , also z.B. "DDR0", wenn ich "DD" tippe! Beim STM32 heißt die .h anders, aber das Prinzip ist das Gleiche. Hans-Georg Lehnard schrieb: > Den Thread kann man, meiner Meinung nach, aber offen lassen! Ja! Ich will weiter lernen! :-)
:
Bearbeitet durch User
Torsten C. schrieb: > > Zur Frage des OP fehlen m.E. nur noch ein "Zustandsautomat" und ein > "Timer". > > Ein Timer wäre ja einfach 'ne weitere Klasse, die ohne template<> nach > dem gleichen Schema wie "class Pin"^^ aufgesetzt wird. > Das sehe ich nicht ganz so ;) Einen Zustandsautomaten kann man einsetzen aber ob der so effektiv ist wie Pedas entprellroutine würd ich jetzt nicht unterschreiben. Und wie kommt die Zeit in dein IO Port ? Da brauchst du eine Callback Funktion entweder direkt aus der ISR aufgerufen oder besser über eine Klasse SystemTimer die einen Hardware Timer verwaltet. Dann wäre es schön, wenn der Pin sich beim SystemTimer anmelden könnte und dabei noch sagen in welchem Intervall er gerne einen Rückruf hätte. Vielleicht sogar während der Laufzeit umschaltbar. Aha, höre ich dich sagen: Das ist eine Aufgabe für das Observer Pattern. Richtig aber dann bitte in einer Embedded Version ;) Dann Brauchst du Klassen für Tasten: SimpleButton // taste gedrückt ja/nein EdgeButton // Taste seit der letzten Abfrage neu gedrückt TimedButton // Taste kurz, mittel, lang gedrückt. Und jetzt noch eine Super Led, die alles kann // ein, aus, schnell, langsam blinken. Die IO Ports über die wir bisher geschrieben haben geben das nicht her. Damit wäre für mich Pedas Frage beantwortet ;) main { SimpleButton simplBtn(PORTA,P1); EdgeButton edgeBtn(PORTA,P2); TimedButton timedBtn(PORTA,P3); // led ist default aus SuperLed led1(PORTA,P4); SuperLed led2(PORTA,P5); SuperLed led3(PORTA,P6); while(1) { led1 = simplBtn // Led Ein wenn gedrückt led2 = edgeBtn // Led togelt bei jedem Tastendruck led3 = timedBtn // kurz gedrückt led aus, mittel gedrückt led // blinkt schnell, lang gedrückt led blinkt langsam } } Und dann schaun wir mal in welchen MC das noch rein passt ;)
Ich habe hier noch ein Entwicklungsbord mit AT91sam9260 und der hat 22 *32Bit Register für ein PORT ! ... Soweit bin ich aber noch nicht .. ;)
Mit dem automatischen hochzählen der Register und nur das erste definieren wäre ich vorsichtig. Falls Atmel aud die Idee kommt, bei der nächsten Umstellung auf einen anderen Produktionsprozess und mit einer Maskenrevision die Reihenfolge ändert hast du verloren. Alles schon vorgekommen !
Hans-Georg Lehnard schrieb: > Mit dem automatischen hochzählen der Register und nur das erste > definieren wäre ich vorsichtig. Mit Startadresse meinte ich die Startadresse für die structs "AvrPortRegsTyp1"^^ oder "AvrPortRegsTyp2". Sorry, also nicht "genau so"^^, sondern "so ähnlch". Danke für den Hinweis! Es könnte dann je ein Array mit UARTs, ein Array mit Timern eins mit DMA-Kanälen usw. geben. Ich muss das in der Praxis ausprobieren, das kann ich mir im Moment nicht 100% vorstellen. Vielleicht wird's 'ne eigene Klasse, wo alle UARTs, Timer, DMA-Kanäle usw. in einer static const drin sind. Diese Klasse wird ja dann nur zur Compile-Zeit von constexpr-Funktionen gebraucht, landet also nicht im µC.
:
Bearbeitet durch User
Hallo Torsten, Torsten C. schrieb: > Und die berechneten Werte werden in dem Code aus Deinem o.g. Beitrag im > ctor der "class Pin" den folgenden Pointern zugewiesen: > >
1 | volatile uint8_t * const ddr; |
2 | > volatile uint8_t * const port; |
3 | > volatile uint8_t * const pin; |
Pointer, Zeiger, Adressen... nenn' es wie Du willst. Tatsache ist, daß Du Deinem Compiler irgendwie sagen mußt, daß die Variable keine Variable im herkömmlichen Sinne, sondern die Adresse eines Registers ist, auf das er nur über ebendiese hart codierte, konstante Adresse zugreifen kann. > Ich habe nur ein "const" ergänzt, wegen "ichbinmaldiesmaldas"^^. Die Fehler, die das verhindert, erscheinen mir ziemlich hypothetisch, so daß der echte, praktische Nutzen sich IMHO in Grenzen hält. Aber es ist ein wenig eleganter und offensichtlicher (Lesbarkeit!) und kostet nichts, insofern habe ich die Änderung in mein "typedef _Register" übernommen. > Man könnte dem ctor die Makros "DDRB" usw. übergeben oder die Adressen > anders berechnen. Bei "anders" muss man - je nach Methode - "constexpr" > benutzen, bei enums^^ wäre man sogar typsicher. Beides geht. Ja. Man kann auch das Rad neu erfinden und sich dann in den Fuß schießen, das geht auch. Die Frage ist aber doch, ob das auch klug ist. ;-) Solche Konstanten werden in der Regel nur ein Mal verwendet, nämlich, wenn eine Instanz von Pin erzeugt wird. Wenn Du das anders machen willst, wirst Du nicht umhin kommen, eigene Libraries für verschiedene AVRs zu schreiben und sie dauerhaft zu pflegen. Ob sich der Aufwand dafür lohnt, um Fehlern vorzubeugen, die bei näherem Hinsehen a) relativ unwahrscheinlich und b) ausgesprochen leicht zu finden sind, sei dahingestellt. Aber unter dem Stichwort der Wiederverwendbarkeit halte ich es für eine bessere Idee, hier einfach die bereits vorhandenen Makros aus der AVR-Libc zu benutzen. Die wird nämlich dankenswerterweise von anderen erstellt und gepflegt, und solange es keine gravierenden Nachteile bedingt, nehme ich diese großzügige Schenkung ausgesprochen gerne an. Faulheit ist laut Larry Wall schließlich eine der Kardinaltugenden guter Programmierer. ;-) Liebe Grüße, Karl
Karl Käfer schrieb: > Die Fehler, die das verhindert, erscheinen mir ziemlich hypothetisch Es geht nicht um Fehler, sondern um "final und private"^^. Eigentlich egal, denn Du hast die Änderung ja bereits übernommen. Karl Käfer schrieb: > Die Frage ist aber doch, ob das auch klug ist. Über Geschmacksfragen kann man nicht streiten. Mich stören die Vorschläge vom Editor^^ und die Verschmutzung des globalen "Namensraums", wenn ich das so nennen darf. Karl Käfer schrieb: > unter dem Stichwort der Wiederverwendbarkeit halte ich es für eine > bessere Idee, hier einfach die bereits vorhandenen Makros aus der > AVR-Libc zu benutzen Ja, das sehe ich genau so. Entweder man holt sich die Startadressen der struct-Pointer (z.B. auf AvrPortRegsTyp1^^) aus der AVR-Libc oder bastelt eine automatische Konvertierung in Klassen mit "static constexpr".
:
Bearbeitet durch User
Karl Käfer schrieb: > > Solche Konstanten werden in der Regel nur ein Mal verwendet, nämlich, > wenn eine Instanz von Pin erzeugt wird. Wenn Du das anders machen > willst, wirst Du nicht umhin kommen, eigene Libraries für verschiedene > AVRs zu schreiben und sie dauerhaft zu pflegen. Ob sich der Aufwand > dafür lohnt, um Fehlern vorzubeugen, die bei näherem Hinsehen a) relativ > unwahrscheinlich und b) ausgesprochen leicht zu finden sind, sei > dahingestellt. > > Aber unter dem Stichwort der Wiederverwendbarkeit halte ich es für eine > bessere Idee, hier einfach die bereits vorhandenen Makros aus der > AVR-Libc zu benutzen. Die wird nämlich dankenswerterweise von anderen > erstellt und gepflegt, und solange es keine gravierenden Nachteile > bedingt, nehme ich diese großzügige Schenkung ausgesprochen gerne an. > Faulheit ist laut Larry Wall schließlich eine der Kardinaltugenden guter > Programmierer. ;-) > Hallo Karl, natürlich hast du recht aber bei den AVR ist es nicht so schlimm, denn die mitgelieferten xml Files parsen und in einem passenden Format speichern dürfte eine der leichteren Übungen sein ;)
Torsten C. schrieb: > Und die berechneten Werte werden in dem Code aus Deinem o.g. Beitrag im > ctor der "class Pin" den folgenden Pointern zugewiesen: > volatile uint8_t * const ddr; > volatile uint8_t * const port; > volatile uint8_t * const pin; > Wie man sieht: Die gewohnten Namen sind wiedererkennbar! Es resultiert daraus aber nur kurzer Code mit Direktzugriff auf die Ports (z.B. 'sbi'/ 'cbi') in den aufgeführten Minimalbeispielen, wo alles in einer Datei steht! Torsten C. schrieb: > Das man um "diese <>-Klammerlisten" herum kommt, falls mann will, > wurde ja an "class Pin" von Karl Käfer gezeigt, oder sind da noch Fragen > offen? siehe oben. Hans-Georg Lehnard schrieb: > Mit dem automatischen hochzählen der Register und nur das erste > definieren wäre ich vorsichtig. Falls Atmel aud die Idee kommt, bei der > nächsten Umstellung auf einen anderen Produktionsprozess und mit einer > Maskenrevision die Reihenfolge ändert hast du verloren. Alles schon > vorgekommen ! Das gibt's schon! Deshalb: <komt morgen>
Ralf G. schrieb: > Es resultiert daraus aber nur kurzer Code mit Direktzugriff auf die > Ports (z.B. 'sbi'/ 'cbi') in den aufgeführten Minimalbeispielen, wo > alles in einer Datei steht! Das verstehe ich nicht. Die Anzahl der Dateien doch egal, mit "const", "private" oder "final". Hast Du die Beiträge ^^ gelesen? Ohne "const", "private" oder "final" wären 'sbi' oder 'cbi' nicht wirklich sicher, da hättest Du Recht, genau deshalb ja "const".
:
Bearbeitet durch User
Torsten C. schrieb: > Hans-Georg Lehnard schrieb: >> Mit dem automatischen hochzählen der Register und nur das erste >> definieren wäre ich vorsichtig. > > Mit Startadresse meinte ich die Startadresse für die structs > "AvrPortRegsTyp1"^^ oder "AvrPortRegsTyp2". Sorry, also nicht "genau > so"^^, sondern "so ähnlch". Danke für den Hinweis! > > Es könnte dann je ein Array mit UARTs, ein Array mit Timern eins mit > DMA-Kanälen usw. geben. Ich muss das in der Praxis ausprobieren, das > kann ich mir im Moment nicht 100% vorstellen. > > Vielleicht wird's 'ne eigene Klasse, wo alle UARTs, Timer, DMA-Kanäle > usw. in einer static const drin sind. Diese Klasse wird ja dann nur zur > Compile-Zeit von constexpr-Funktionen gebraucht, landet also nicht im > µC. Denk nicht so kompliziert;) Eine Uart ist nur eine Ausprägung einer AVR USART die muss nicht immer eine Uart sein sondern kann SPI und I2C sein oder sonst was sein. Oder du hast eine Software Uart zusätzlich. Oder eine UART mit 9 bit Protokoll usw. da kommst du mit deinen Arrays nicht mehr hinterher ;) So jetzt bin ich mal wieder ruhig ... ,)
Torsten C. schrieb: > Hast Du die Beiträge ^^ gelesen? Das hat mit 'const' nichts zu tuen. Ich hab's ausprobiert.
Hans-Georg Lehnard schrieb: > Eine Uart ist nur eine Ausprägung einer AVR USART die muss nicht immer > eine Uart sein sondern kann SPI und I2C sein oder sonst was sein. > Oder du hast eine Software Uart zusätzlich. Und dann müsste ein SPI-ctor oder I2C-ctor statt eines UART-ctors aufgerufen werden. So lange die Methoden-Bezeichner identisch sind, geht das ohne Vererbung flexibel. Passt! Oder? BTW, blöde Frage: Woran erkennt der Compiler, dass 'sbi' oder 'cbi' anwendbar sind? Oder geht das bei allen Adressen im RAM?
:
Bearbeitet durch User
Torsten C. schrieb: > Woran erkennt der Compiler, dass 'sbi' oder 'cbi' anwendbar sind? Oder > geht das bei allen Adressen im RAM? Da kann ich ja jetzt mal fragen: "Hast du das Datenblatt gelesen?" :-( Genau darum geht es die ganze Zeit. Effizienter Code. Mit Zeigern und virtuellen Funktionen um sich schmeißen, kann jeder!
:
Bearbeitet durch User
Ralf G. schrieb: > Da kann ich ja jetzt mal fragen: "Hast du das Datenblatt gelesen?" Das ist lange her! Ich hatte nur grob in Erinnerung, dass das nicht überall im RAM geht, daher die Frage, ob wir was übersehen haben. Ich hätte auch morgen nachlesen und danach posten können. Sorry, es war ja auch nur "by the way". Aber vielleicht wichtig, wie Du selbst sagst.
:
Bearbeitet durch User
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > wir wollen schon das gleiche .. "sprechende Namen" > [...] > struct AvrPortRegsTyp1 { > uint8_t in; // PINx input register > uint8_t dir; // DDRx input register > uint8_t out; // DATAx output register > }; > [...] > Und definiere dann meine Ports als Konstante . > > #ifdef __AVR_ATmega16__ > constexpr AvrPortRegsTyp1 PORTA = {0x19,0x1A,0x1B}; > #endif // __AVR_ATmega16__ > [...] > Der Unterschied ist aber mein PORTA definiert den Kompletten Port und > nicht nur ein Register und das gefällt mir besser und ich halte es für > stimmiger. Das ist wohl Geschmackssache, aber die Idee gefällt mir. Aber dann müssten wir die Definitionen für alle möglichen AVRs a) erstellen und b) zukünftig pflegen... hm. Liebe Grüße, Karl
Karl Käfer schrieb: > Aber dann müssten wir die Definitionen für alle möglichen AVRs > a) erstellen und > b) zukünftig pflegen... hm. Wie gesagt: Entweder in einer constexpr-Funktion (kompliziert aber sehr dynamisch) die .xml- oder .h-Dateien parsen oder ein Script basteln: "eine der leichteren Übungen"^^. Letzteres muss ja nur einmal pro "Update" ausgeführt werden und das Ergebnis könnte einer einmal in Github aktualisieren. Weitere Ideen? Favorit?
:
Bearbeitet durch User
Torsten C. schrieb: > Sorry. Ja, Okay. Dann mal als Zusammenfassung: 'const' ist deine eigene Absichtserklärung, den Wert (Zeiger/ Variable) nie zu verändern. Der Compiler tritt dir dafür in... (du weißt schon). Wie der Compiler das intern umsetzt, ist sein Bier. Die 'Daten' für den Port (DDRx, PORTx, PINx) in eine Klasse zu packen ist erstmal sinnvoll. Jetzt kommt es darauf an wie. Zeiger, Referenzen, gerne auch 'const'... ist völlig egal. Sobald das Projekt über mehrere Module geht, ist es nicht mehr zu erkennen, dass es sich um Zeiger auf den IO-Bereich handelt. Der Compiler macht 'ganz normale' Speicherzugriffe daraus. Normalerweise macht das nichts. Dauert es eben ein paar Takte länger. Jetzt ist aber die Frage: 'Geht das trotzdem anders?' Da bieten sich möglicherweise! Templates an. Da sehe ich allerdings die Grenze: Wenn man jetzt mit den IO_Pin-Klassen weiterarbeiten will, müsste man den ganzen '<>-Rattenschwanz' mitnehmen. Das gefällt mir nicht!!! Ich hab trotzdem mal was probiert. Stichwort 'virtuelle Funktion'. Gefällt mir auch nicht!!!: zusätzlicher RAM-Verbrauch, zusätzlicher ROM-Verbrauch, Geschwindigkeitsvorteil gegenüber Zeigern mit 'normalen Speicherzugriffen' sehr zweifelhaft! Eigentlich wollte ich mal noch ein paar andere Varianten vor der Veröffentlichung probieren, aber ich stell's heute schon mal ein:
1 | #include <avr/io.h> |
2 | #include <stdarg.h> |
3 | |
4 | |
5 | // ********************************************************************************
|
6 | // templates (für 'Spezial'-Enums)
|
7 | //
|
8 | template<class T> |
9 | inline T operator|(const T a, const T b) |
10 | {
|
11 | return static_cast<T>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b)); |
12 | };
|
13 | // --------------------------------------------------------------------------------
|
14 | template<class T> |
15 | inline T operator&(const T a, const T b) |
16 | {
|
17 | return static_cast<T>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b)); |
18 | };
|
19 | // ********************************************************************************
|
20 | extern "C" |
21 | {
|
22 | void __cxa_pure_virtual() |
23 | {
|
24 | // erst mal nichts weiter
|
25 | }
|
26 | }
|
27 | // ********************************************************************************
|
28 | enum e_pinInit |
29 | {
|
30 | pin0 = 0x01, |
31 | pin1 = 0x02, |
32 | pin2 = 0x04, |
33 | pin3 = 0x08, |
34 | pin4 = 0x10, |
35 | pin5 = 0x20, |
36 | pin6 = 0x40, |
37 | pin7 = 0x80, |
38 | pin03 = 0x0F, |
39 | pin47 = 0xF0, |
40 | pin07 = 0xFF, |
41 | pinPullUp = 0x100 |
42 | };
|
43 | // --------------------------------------------------------------------------------
|
44 | // AVR Port
|
45 | template< uint16_t sfr_pin, uint16_t sfr_ddr, uint16_t sfr_port> |
46 | struct s_AvrPort |
47 | {
|
48 | static const uint16_t pin = sfr_pin; |
49 | static const uint16_t dir = sfr_ddr; |
50 | static const uint16_t port = sfr_port; |
51 | };
|
52 | // --------------------------------------------------------------------------------
|
53 | #define PORT(x) typedef s_AvrPort<(uint16_t)&PIN##x,(uint16_t)&DDR##x,(uint16_t)&PORT##x> port##x;
|
54 | |
55 | #ifdef PORTA
|
56 | PORT(A) |
57 | #endif
|
58 | #ifdef PORTB
|
59 | PORT(B) |
60 | #endif
|
61 | #ifdef PORTC
|
62 | PORT(C) |
63 | #endif
|
64 | #ifdef PORTD
|
65 | PORT(D) |
66 | #endif
|
67 | #ifdef PORTE
|
68 | PORT(E) |
69 | #endif
|
70 | #ifdef PORTF
|
71 | PORT(F) |
72 | #endif
|
73 | #ifdef PORTG
|
74 | PORT(G) |
75 | #endif
|
76 | // --------------------------------------------------------------------------------
|
77 | // Zugriff auf SFR Register
|
78 | class SfrRegAccess |
79 | {
|
80 | public:
|
81 | static uint8_t Get(const uint16_t reg, uint8_t bits) |
82 | {
|
83 | return _SFR_MEM8(reg) & bits; |
84 | };
|
85 | static void Set(const uint16_t reg, uint8_t bits, uint8_t mask) |
86 | {
|
87 | if (mask > 1) |
88 | {
|
89 | _SFR_MEM8(reg) = (_SFR_MEM8(reg) & ~mask) | (bits & mask); |
90 | return; |
91 | }
|
92 | else if ( mask) |
93 | _SFR_MEM8(reg) |= bits; |
94 | else
|
95 | _SFR_MEM8(reg) &= ~bits; |
96 | };
|
97 | static void Toggle(const uint16_t reg, uint8_t bits) |
98 | {
|
99 | _SFR_MEM8(reg) ^= bits; |
100 | };
|
101 | };
|
102 | // --------------------------------------------------------------------------------
|
103 | class c_OutputPinsBase |
104 | {
|
105 | private:
|
106 | public:
|
107 | virtual void On(e_pinInit mask) = 0; |
108 | virtual void On() = 0; |
109 | virtual void Off(e_pinInit mask) = 0; |
110 | virtual void Off() = 0; |
111 | virtual void Toggle(e_pinInit mask) = 0; |
112 | virtual void Toggle() = 0; |
113 | virtual uint8_t GetPins() = 0; |
114 | virtual void Out(uint8_t value) = 0; |
115 | };
|
116 | // --------------------------------------------------------------------------------
|
117 | template <typename sfr_regs> |
118 | class c_OutputPins : c_OutputPinsBase |
119 | {
|
120 | private:
|
121 | const uint8_t _pins; |
122 | public:
|
123 | c_OutputPins(e_pinInit pins) : c_OutputPinsBase(), _pins(pins) |
124 | {
|
125 | SfrRegAccess::Set(sfr_regs::dir, pins & pin07, 1); |
126 | };
|
127 | void On(e_pinInit mask) { SfrRegAccess::Set(sfr_regs::port, mask & (_pins & pin07), 1); }; |
128 | void On() { SfrRegAccess::Set(sfr_regs::port, _pins & pin07, 1); }; |
129 | void Off(e_pinInit mask) { SfrRegAccess::Set(sfr_regs::port, mask & (_pins & pin07), 0); }; |
130 | void Off() { SfrRegAccess::Set(sfr_regs::port, _pins & pin07, 0); }; |
131 | void Toggle(e_pinInit mask) { SfrRegAccess::Toggle(sfr_regs::port, mask & (_pins & pin07)); }; |
132 | void Toggle() { SfrRegAccess::Toggle(sfr_regs::port, _pins & pin07); }; |
133 | uint8_t GetPins() { return _pins; }; |
134 | void Out(uint8_t value) { SfrRegAccess::Set(sfr_regs::port, value, _pins & pin07); }; |
135 | };
|
136 | // --------------------------------------------------------------------------------
|
137 | class c_InputPinsBase |
138 | {
|
139 | private:
|
140 | public:
|
141 | virtual uint8_t In(uint8_t mask) = 0; |
142 | virtual uint8_t In() = 0; |
143 | };
|
144 | // --------------------------------------------------------------------------------
|
145 | template <typename sfr_regs> |
146 | class c_InputPins : c_InputPinsBase |
147 | {
|
148 | private:
|
149 | const uint8_t _pins; |
150 | public:
|
151 | c_InputPins(e_pinInit pins) : c_InputPinsBase(), _pins(pins) |
152 | {
|
153 | if (pins & pinPullUp) |
154 | SfrRegAccess::Set(sfr_regs::port, pins & pin07, 1); |
155 | };
|
156 | uint8_t In(uint8_t mask) { return SfrRegAccess::Get(sfr_regs::pin, mask & (_pins & pin07)); }; |
157 | uint8_t In() { return SfrRegAccess::Get(sfr_regs::pin, _pins & pin07); }; |
158 | };
|
159 | // --------------------------------------------------------------------------------
|
160 | class c_LCD |
161 | // nur zum Test, für ein paar mehr oder weniger sinnvolle Ausgaben
|
162 | {
|
163 | private:
|
164 | c_OutputPinsBase *_data; |
165 | c_OutputPinsBase *_E; |
166 | c_OutputPinsBase *_RS; |
167 | c_OutputPinsBase *_RW; |
168 | public:
|
169 | c_LCD(c_OutputPinsBase *d,c_OutputPinsBase *E,c_OutputPinsBase *RS,c_OutputPinsBase *RW) : _data(d), _E(E), _RS(RS), _RW(RW) {}; |
170 | void DataOut(uint8_t value) |
171 | {
|
172 | _data->Out(value); |
173 | }
|
174 | void Enable(uint8_t value) |
175 | {
|
176 | if (value) |
177 | _E->On(); |
178 | else
|
179 | _E->Off(); |
180 | }
|
181 | };
|
182 | // --------------------------------------------------------------------------------
|
183 | |
184 | |
185 | int main(void) |
186 | {
|
187 | c_OutputPins<portA> data(pin5|pin4|pin3|pin2); |
188 | c_OutputPins<portB> e(pin1); |
189 | c_OutputPins<portB> rs(pin2); |
190 | c_OutputPins<portB> rw(pin0); |
191 | |
192 | c_LCD LCD((c_OutputPinsBase*)&data,(c_OutputPinsBase*)&e,(c_OutputPinsBase*)&rs,(c_OutputPinsBase*)&rw); |
193 | |
194 | uint8_t start = 0xA0; |
195 | while(1) |
196 | {
|
197 | LCD.DataOut(start++); |
198 | if ((start & 0x07) == 0x07) |
199 | LCD.Enable(1); |
200 | else
|
201 | LCD.Enable(0); |
202 | }
|
203 | }
|
Vor allen Dingen: Das ist nicht das, was ich mir unter effizient vorgestellt habe. Vielleicht hat ein Profi 'ne bessere Idee.
Ralf G. schrieb: > Vielleicht hat ein Profi 'ne bessere Idee. Ich bin kein "Profi", aber falls das niemand vor mir macht, werde ich gelegentlich folgendes mal mit verschiedenen Compilern analysieren: Ralf G. schrieb: > Zeiger, Referenzen, gerne auch 'const'... ist völlig egal Ich bin fest davon überzeugt, dass sich ein "const" nicht anders auswirkt als ein #define. Das steht jedenfalls überall im I-Net. Daher bin ich davon ausgegangen, dass das nicht völlig egal ist. Ohne "const", "final" oder "private" (^^gelesen?) hättest Du mit Sicherheit Recht und sobald das Projekt über mehrere Module ginge, könnte der Compiler 'ganz normale' Speicherzugriffe daraus machen. Aber ich würde mich (wie mehrfach gesagt^^) nicht darauf verlassen, dass er das nie tut, daher "const", "final" oder "private".
:
Bearbeitet durch User
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > natürlich hast du recht aber bei den AVR ist es nicht so schlimm, > denn die mitgelieferten xml Files parsen und in einem passenden Format > speichern dürfte eine der leichteren Übungen sein ;) Mitgelieferte XML Files... huch. Habe gerade mal einen Download von Atmel Studio angeworfen, sowas hab' ich hier nämlich nicht -- ich habe auch das Betrübssystem nicht, auf dem das läuft. Mal schauen, ob ich das irgendwie entpackt bekomme, sonst muß ich mir morgen eine Wegwerf-VM machen. Gute Nacht, Karl
Karl Käfer schrieb: > sowas hab' ich hier nämlich nicht Kenne ich auch nicht, hatte mich auf Hans-Georg bezogen: Kläre uns mal auf! xpcc hätte die XML-Files und .h kann man zur Not auch selbst parsen. Für XML gäbe es halt fertige Parser, das wäre bequemer; das ist aber am Ende "Conchita", wie die Ösis sagen, also "Wurst"! Karl Käfer schrieb: > Gute Nacht Danke, wünsche ich Dir auch!
:
Bearbeitet durch User
@ekiwi, Super Sache, die ihr da macht. @Peter Danneger Peter Dannegger schrieb: > Ich möchte gerne folgendes erreichen: LED0 = KEY0.state; // > an solange Taste gedrückt > LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke > LED2.off = KEY2.press_short; // kurz drücken - aus > LED2.on = KEY2.press_long; // lang drücken - an Ersetze einfache LED1.off mit LED1::setOutput(xpcc::Gpio::Low); ;-) Die Syntax LED1.off beruht irgendwie auf der Annahme, daß man SFR mit struct abstrahiert hat. Ist aber nur eine Vorstufe der Objektorientierung. Interessant wird es erst, wenn Funktionen dazukommen.
Die xml Files findest du unter windows .... \Atmel\Atmel Studio 6.2\devices
frischling schrieb: > Objekte kann man doch auch statisch anlegen. Hmmm, muss man das eigentlich tun? noreply@noreply.com schrieb: > LED1::setOutput(xpcc::Gpio::Low); Die Variante hatten wir noch gar nicht, also ohne was statisch bzw. als Klassen-Instanz mit const-Pointern anzulegen. Oder? Das würde vllt. mein Singleton^^-Problem lösen, aber das blicke ich heute N8 nicht mehr.
:
Bearbeitet durch User
Torsten C. schrieb: > Es könnte dann je ein Array mit UARTs, ein Array mit Timern eins mit > DMA-Kanälen usw. geben. Hmm... meinst du in etwa so, wie Atmel das bei den Cortex-Controlllern gemacht hat?
1 | // Timer
|
2 | TC0->TC_CHANNEL[0].HierRegisterName // Timer 0 |
3 | TC0->TC_CHANNEL[1].HierRegisterName // Timer 1 |
4 | TC0->TC_CHANNEL[2].HierRegisterName // Timer 2 |
5 | TC1->TC_CHANNEL[0].HierRegisterName // Timer 3 |
6 | TC1->TC_CHANNEL[1].HierRegisterName // Timer 4 |
7 | TC1->TC_CHANNEL[2].HierRegisterName // Timer 5 |
8 | TC2->TC_CHANNEL[0].HierRegisterName // Timer 6 |
9 | TC2->TC_CHANNEL[1].HierRegisterName // Timer 7 |
10 | TC2->TC_CHANNEL[2].HierRegisterName // Timer 8 |
Torsten C. schrieb: > noreply@noreply.com schrieb: >> LED1::setOutput(xpcc::Gpio::Low); > > Die Variante hatten wir noch gar nicht, also ohne was statisch bzw. als > Klassen-Instanz mit const-Pointern anzulegen. Oder? Meinst du ohne Instanzierung? Das gabs in meinem Beispiel ganz weit oben schoneinmal. Schaut sich das denn wirklich niemand an???
Daniel A. schrieb: > Schaut sich das denn wirklich niemand an??? Nein, hier geht es durcheinander wie Kraut und Rüben es dreht sich alles im Kreis aber alle machen mit. Aber wie willst du das Online mit den verschieden Kentnissen und Interessen lösen ?
Hans-Georg Lehnard schrieb: > Nein, hier geht es durcheinander wie Kraut und Rüben es dreht sich alles > im Kreis aber alle machen mit. Gute Zusammenfassung. Es ist für den stillen Mitleser sehr unterhaltsam, zu sehen, wie einige aneinander komplett vorbeireden und sich überhaupt nicht richtig konzentrieren auf das, was der andere gerade geschrieben bzw. schon als Lösung aufgezeigt hat. > Aber wie willst du das Online mit den verschieden Kentnissen und > Interessen lösen ? So jedenfalls nicht. Das sag ich Dir aus eigener Erfahrung. Mein Vorschlag als Denkanstoß: - Machs erstmal alleine oder schließ Dich mit einem oder maximal zwei Leuten, die Ahnung von der Materie haben, per Mail kurz. - Erstelle (allein oder in der kleinen Gruppe) ein Konzept, was Hand und Fuß hat. - Erstelle erste Ansätze von Lösungen als nachvollziehbare Prototypen. - Stelle das Konzept, den Prototyp und den Weg zur Vorgehensweise hier vor. - Gewinne damit andere, die genau diesen Weg mitgehen wollen und lass Dir vor allem nicht mehr reinquatschen, dass man es auch auf tausend anderen Wegen machen könnte. - Setze dann mit den anderen im Team dieses Ziel um. Ich habe einige (auch größere) Open-Source-Projekte aus dem Boden gestampft und dann online mit bis zu 70 Leuten im Team umgesetzt und gepflegt - viele Jahre lang. Ich sag Dir: Es geht nur so wie oben beschrieben. Einer muss voranschreiten und die Richtung vorgeben.
:
Bearbeitet durch Moderator
Hans-Georg Lehnard schrieb: > Aber wie willst du das Online mit den verschieden Kentnissen und > Interessen lösen ? 1) Die Interessen gruppieren und die Gruppen in Github projekte aufteilen. 2) Die github projekte unter eine Lizenz wie z.B. MIT stellen, damit alle voneinander kopieren können. 3) Die vor und nachteile der verschiedenen Umsetzungen im bezug auf die Kriterien Komplexität, Performance, Usabillity, Erweiterbarkeit, etc. diskutieren 4) Erfahrungen für zukünftige projekte nutzen/Lieblingslib verwenden
Frank M. schrieb: > - Machs erstmal alleine oder schließ Dich mit einem oder maximal > zwei Leuten, die Ahnung von der Materie haben, per Mail kurz. > Lieber Frank, auch du hast nicht alles gelesen ;) Thorsten will ein Projekt ich nicht. Für mich stellt sich eher die Frage: Wie kann jemand, ohne spezielle Kenstnisse über die Feinheitem von C++(11) und Meta-Templates, diesen Thread lesen und was davon mitnehmen. Dieser Thread motiviert doch nicht Cpp einzusetzen !! Sondern jeder Leser denkt doch: "Alles viel zu kompliziert, nichts für mich" ich mach weiter wie bisher. Peda hat nach einer verständlichen Anleitung gefragt und das ist dieser Thead mit Sicherheit, bisher jedenfalls, nicht.
Hans-Georg Lehnard schrieb: > Lieber Frank, > auch du hast nicht alles gelesen ;) Ja, weil mich das Thema nur am Rande interessiert. Eine solche C++-Lib ist für mich nicht lebensnotwendig, auch wenn ich ihr eine gewisse Attraktivität nicht absprechen könnte ;-) > Thorsten will ein Projekt ich nicht. (BTW: Lass mal das 'h' aus Torstens Namen raus. Das irritiert mich doch jedesmal sehr und ich denke: "Nanu, noch einer, der mitmischt?") Okay, ich wusste nicht bzw. habe bisher nicht herauslesen können, dass Du das Projekt gar nicht willst ;-) Aber egal. Die oben von mir genannten Punkte sind nicht personenbezogen. Kann man auf jeden übertragen. Ich muss aber dann noch einen Punkt hinzufügen: - Man muss als Vordenker von der Materie jede Menge Ahnung haben. > Für mich stellt sich eher die Frage: > > Wie kann jemand, ohne spezielle Kenstnisse über die Feinheitem von > C++(11) und Meta-Templates, diesen Thread lesen und was davon mitnehmen. Man kann nur Fetzen davon mitnehmen. Wenn man nicht täglich mit C++ arbeitet, sollte man erstmal die Basis lernen, bevor man sich auf die Details von C++(11) stürzt. Sonst kommt einem viel als total neu und aufregend vor, was tatsächlich schon ein alter Hut ist - siehe C++-Templates im allgemeinen. > Dieser Thread motiviert doch nicht Cpp einzusetzen !! > Sondern jeder Leser denkt doch: "Alles viel zu kompliziert, nichts für > mich" ich mach weiter wie bisher. Eben. Das liegt in der Natur der Sache - sprich an der Natur eines Forums. Wenn jemand einen Thread eröffnet, dann tragen 10% der Mitleser etwas zum Thema bei, 30% versuchen, es Dir auszureden, weil man es anders und viel besser machen könnte, weitere 30% haben es überhaupt nicht verstanden und der Rest amüsiert sich nur über die Typen, die da aufeinander losquasseln. > Peda hat nach einer verständlichen Anleitung gefragt und das ist dieser > Thead mit Sicherheit, bisher jedenfalls, nicht. Es gibt halt Leute, die finden zunächst die Fragestellung spannend und versuchen dann, schon mal weiter zu denken. Sie kommen dann irgendwann auf die geniale Idee, dass dieses Problem (und weitere zweitausend ähnlich gelagerte Fälle) nur mit einem revolutionären Rundumschlag zu lösen ist. Alsbald kommt man von Hölzchen auf Stöckchen, hat längst die zugrundeliegende Frage bzw. Aufgabenstellung verdrängt und verkrümelt sich in Details von Randproblemen, die plötzlich in den Vordergrund drängen. Danach werden dann die Prioritäten verschoben und man begeistert sich plötzlich nur noch für diese Detailprobleme, die jetzt erstmal absoluten Vorrang haben, weil sie so enorm spannend sind. Man mag sich nun fragen: "Was soll das?" Die Antwort ist einfach: Gar nichts. Irgendwann findet derjenige "geniale Geist" ein komplett anderes Gebiet spannender ("z.B. wie bekomme ich Bananen wieder gerade?") und das Thema und alle bisher dazu erstellten Gedanken und Ideen werden einfach in den Orkus gespült. Und so hangelt man sich von einer Aufgabe zur nächsten durchs Leben - ohne irgend etwas tatsächlich fertigzustellen. Ich gebe Dir abschließend nur einen Rat: Wenn Du dieses Projekt gar nicht willst, dann lass es einfach. Gespräche mit Blinden über Farben sind nicht zielführend.
:
Bearbeitet durch Moderator
Hallo Frank M, ich zitiere mich jetzt mal selbst ;) Ich will keine allgemeine Library schreiben, für mich ist das eine reine private Machbarkeitsstudie und Auffrischung meiner C++ Kentnisse. Ansonsten sehe ich das genau so wie du. Manchmal habe ich den Eindruck, solche Foren sind reine Selbsthifegruppen von "Nerds" ;)
Karl Käfer schrieb: > Ach, Torsten... Jedes Mal, wenn Du etwas idiotensicher machen willst, > dann kommt die Natur und erfindet bessere Idioten. Danke für den "Gegenwind" in den darauf folgenden Argumenten. In der Industrie werden ja auch noch solche code-review-tools eingesetzt: http://en.wikipedia.org/wiki/List_of_tools_for_static_code_analysis#C.2FC.2B.2B Wenn man wollte, käme man damit wahrscheinlich sogar weiter, als sich auf compiler- oder Linker-Fehler allein zu verlassen. Ich bin eigentlich ganz froh, wenn ich mir darüber im Moment nicht auch noch Gedanken machen muss. Frank M. schrieb: > Gespräche mit Blinden über Farben sind nicht zielführend. Ich hoffe, dass ich nicht mit blind gemeint bin. Ich lerne aus den Gesprächen sehr viel. Frank M. schrieb: > - Man muss als Vordenker von der Materie jede Menge Ahnung haben. Hans-Georg Lehnard schrieb: > Torsten will ein Projekt ich nicht. Das ist nicht ganz falsch, denn da steht ja nicht "Torsten will ein Projekt starten", denn für einen einen "Vordenker" reicht meine Erfahrung mit C++ noch nicht. Ich hätte schon gern irgendwann eine Bibliothek für AVR und ARM gemeinsam und mit C++. Ich habe hier viele interessante Denkanstöße bekommen, die ich erstmal ausprobieren muss, um weiter zu kommen. Ich muss jetzt z.B. mal ausprobieren, ob sich bei "Containment" bzw. "Objekt-Komposition" (siehe "Quadrat vs. Rechteck"^^) die neue Klasse auch problemlos statisch anlegen lässt, wenn man nur mit const arbeitet und im ctor alle Pointer der gesamten Komposition initialisieren will.
:
Bearbeitet durch User
Torsten C. schrieb: > Frank M. schrieb: >> Gespräche mit Blinden über Farben sind nicht zielführend. > > Ich hoffe, dass ich nicht mit blind gemeint bin. Du kannst ganz beruhigt sein. Ich habe mich lediglich allgemein über Forum-Threads und deren dynamische Entwicklung geäußert. Das Ende war lediglich ein Fazit, bzw. eine Lehre, die ich schon vor langer Zeit erfahren musste. Ob sich da jemand einen Schuh anzieht oder anziehen will, ist seine alleinige Sache.
:
Bearbeitet durch Moderator
Frank M. schrieb: > Ob sich da jemand einen Schuh anzieht oder anziehen will, ist seine > alleinige Sache. Und Deine "Checkliste" ist dabei sehr hilfreich. :-) Hans-Georg Lehnard schrieb:
1 | while(1) { |
2 | led1 = simplBtn // Led Ein wenn gedrückt |
3 | led2 = edgeBtn // Led togelt bei jedem Tastendruck |
4 | led3 = timedBtn // kurz gedrückt led aus, mittel gedrückt led |
5 | // blinkt schnell, lang gedrückt led blinkt
|
6 | }
|
Wenn das mit Objekt-Komposition^^ klappt, würde ich eine "fest verdrahtete" und eine dynamische Variante anlegen, wo die Zuweisung direkt in der Klasse passiert und nicht in einer While-Schleife.
Torsten C. schrieb: > Frank M. schrieb: >> Ob sich da jemand einen Schuh anzieht oder anziehen will, ist seine >> alleinige Sache. > > Und Deine "Checkliste" ist dabei sehr hilfreich. :-) > > Hans-Georg Lehnard schrieb: >
1 | while(1) { |
2 | > led1 = simplBtn // Led Ein wenn gedrückt |
3 | > led2 = edgeBtn // Led togelt bei jedem Tastendruck |
4 | > led3 = timedBtn // kurz gedrückt led aus, mittel gedrückt led |
5 | > // blinkt schnell, lang gedrückt led blinkt |
6 | > } |
> > Wenn das mit Objekt-Komposition^^ klappt, würde ich eine "fest > verdrahtete" und eine dynamische Variante anlegen, wo die Zuweisung > direkt in der Klasse passiert und nicht in einer While-Schleife. Ach Torsten, diese while Schleife ist doch dein "Hauptpramm" in einer embedded Anwendung.
Hans-Georg Lehnard schrieb: > Ach Torsten, diese while Schleife ist doch dein "Hauptpramm" … Ach Hans-Georg, das ist mir doch klar! Aber was willst Du uns damit sagen?
:
Bearbeitet durch User
Torsten C. schrieb: > Hans-Georg Lehnard schrieb: >> Ach Torsten, diese while Schleife ist doch dein "Hauptpramm" … > > Ach Hans-Georg, das ist mir doch klar! > > Aber was willst Du uns damit sagen? Torsten schrieb : > wo die Zuweisung direkt in der Klasse passiert und nicht > in einer While-Schleife. Eher was wolltest du uns damit sagen ?
Es gibt bereits eine weitverbreitete, erfolgreiche und halbwegs Plattform unabhängige C++ Library für uC: Arduino. Und die zeigt meines Erachtens bestens warum so ein Projekt für eine breite Zielgruppe fast unmöglich ist: - Für Anfänger kann es nicht einfach und idiotensicher genug sein, Performance ist Zweitrangig - ASM Programmierer wollen sowieso jedes Bit selbst kontrollieren, und bekommen einen Herzkasper für jede unnötige Anweisung die der Compiler generiert - Erfahrene uC Programmierer würden schon gerne jedes noch so spezielle Feature ihres ZieluC ausnutzen und genau darüber wissen, was das Framework so in den Registern anstellt. - Reine C Programmierer können sich mit einfacher Objektorientierung noch anfreundne, aber spitze Klammern kennen die nur aus Operatoren für Vergleich und Shift. - Gute C++ Programmierer könnten sicherlich ein Lib aufstellen, die mit so Sachen wie Objektorientierung, Templates, Metaprogrammierung einen hocheffizienten Code erstellt, aber für keine der vorgenannten Gruppen noch zugänglich wäre, weil sie in spitzen Klammern und völlig unverständlichen Compilerfehlermeldungen ertrinken. (Das ist jetzt alles etwas zugespitzt) Arduino hat sich deswegen einzig und allein auf die erste Gruppe konzentriert und sehr großen Erfolg gehabt. Und alle anderen Gruppen schimpfen über ineffizienten, langsamen, großen Code der viele Besonderheiten der Zielplattform versteckt oder überhaupt nicht zugänglich macht.
vielleicht hab ich das problem nicht verstanden, aber beim programmieren meines quadrocopters habe ich überlegt, ob luna, c, oder c++ . als er in c dann geflogen ist, habe ich ein uml gezeichnet, und das dann in c++ mit modellierten klassen umgeschrieben. fliegt natürlich genauso ;-) laufzeitmessungen haben ergeben, daß der c++ - code wenige % länger rechnet als der reine c-code, und im experiment dieser genauso lange wie der in luna geschriebene. die entwicklung scheint mir aber in c++ (klassenorientiert) überlegen, z.b. die nutzung von mehreren objekten der gleichen klasse (z.b. signalfilter) scheint mir für "nicht profis" wie mich in c++ übersichtlicher.
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Nein, hier geht es durcheinander wie Kraut und Rüben es dreht sich alles > im Kreis aber alle machen mit. Aber wie willst du das Online mit den > verschieden Kentnissen und Interessen lösen ? Um das Ganze womöglich wieder in etwas geordnetere Bahnen zu lenken -- und nicht zuletzt, weil konkreter Code IMHO immer noch die beste Grundlage für diese Diskussion ist -- hier meine aktuelle Pin.hpp. Dem geneigten Betrachter wird auffallen, daß: - die Member-Variablen der Basisklasse jetzt nicht mehr "public", sondern "protected" sind - der Typ "_Register" jetzt mit dem Typ-Modifizierer "const" versehen ist, wie angekündigt - die Klasse "InputPin" jetzt Funktionen zum Setzen und Löschen des Pullup-Widerstandes hat - der Konstruktor der Klasse "InputPin" einen optionalen Parameter zum Setzen der Pullups besitzt - der Klasse "OutputPin" die Methode "toggle()" hinzugefügt wurde, die den Pin durch das Schreiben auf das Register PIN umschaltet und - daß die Klasse "InoutPin" zwar wieder enthalten, aber noch nicht um die gerade genannten Funktionen ergänzt worden ist. Hier könnte es sein, daß eine Art "Sicherheitsabfrage" sinnvoll ist, um zum Beispiel zu verhindern, daß der Nutzer die Methode "toggle()" aufruft obwohl der Pin gerade als Input konfiguriert ist. Andererseits würde das den Code größer machen und die Frage aufwerfen, wie ein solcher Fehler abgefangen und / oder darauf reagiert werden kann. Bezüglich des "toggle()"-Verhaltens ist anzumerken, daß "PIN" im Prinzip ein Input-Register ist, bei dem man üblicherweise nicht erwarten würde, daß man darauf schreiben kann. Neuere AVRs erlauben es allerdings, durch Schreiben auf dieses Register ein Umschalten mit nur einem Maschinenbefehl durchzuführen; bei älteren AVRs soll das IIRC nicht gehen. Wenn jemandem bekannt ist, bei welchen AVRs das geht und bei welchen nicht, möge er sich bitte melden. Dann würde ich dort über "#ifdef __AVR_ARCH__" Code einbauen wollen, der abhängig von der AVR-Architektur den korrekten Code einfügt. Der Bequemlichkeit halber habe ich die Datei einmal zum Download angefügt und poste sie zusätzlich noch einmal:
1 | #ifndef _PIN_HPP
|
2 | #define _PIN_HPP 1
|
3 | |
4 | #include <avr/io.h> |
5 | |
6 | /** expands PINDEF(B, 0) to &DDRB, &PORTB, &PINB, PB0
|
7 | * example:
|
8 | * OutputPin ledPin ( PINDEF(B, 0) );
|
9 | * expands to:
|
10 | * OutputPin ledPin (&DDRB, &PORTB, &PINB, PB0);
|
11 | */
|
12 | #define PINDEF(X,Y) &DDR##X,&PORT##X,&PIN##X,Y
|
13 | |
14 | |
15 | typedef volatile uint8_t* const _Register; |
16 | |
17 | |
18 | class Pin { /** base class for a pin */ |
19 | protected:
|
20 | _Register ddr; |
21 | _Register port; |
22 | _Register pin; |
23 | uint8_t num; |
24 | |
25 | public:
|
26 | /** constructor
|
27 | * @param ddr DDRn register address of pin (eg. &DDRB)
|
28 | * @param port PORTn register '' (eg. &PORTB)
|
29 | * @param pin PINn register (eg. &PINB)
|
30 | * @param num pin number (eg. PB2)
|
31 | */
|
32 | Pin(_Register ddr, _Register port, _Register pin, uint8_t num): |
33 | ddr(ddr), port(port), pin(pin), num(num) |
34 | {}
|
35 | };
|
36 | |
37 | |
38 | class InputPin : public Pin { /** an input pin */ |
39 | public:
|
40 | /** constructor
|
41 | * @params see Pin::Pin()
|
42 | */
|
43 | InputPin(_Register ddr, _Register port, |
44 | _Register pin, uint8_t num, bool pullup=false): |
45 | Pin(ddr, port, pin, num) { |
46 | *this->ddr &= ~(1 << this->num); |
47 | if(pullup) { *this->port |= (1 << this->num); } |
48 | }
|
49 | |
50 | /** return true if pin is high, else false */
|
51 | bool isHigh(void) { return (*this->pin & (1 << this->num)); }; |
52 | |
53 | /** return true if pin is low, else false */
|
54 | bool isLow(void) { return !((*this->pin & (1 << this->num))); }; |
55 | |
56 | /** return true if pullup is set, else false */
|
57 | bool getPullup(void) { return (*this->port & (1 << this->num)); } |
58 | |
59 | /** set pullup resistor
|
60 | * @params pullup activate pullup if true
|
61 | */
|
62 | bool setPullup(bool pullup) { |
63 | if(pullup) { *this->port |= (1 << this->num);} |
64 | else { *this->port &= ~(1 << this->num); } |
65 | return this->getPullup(); |
66 | }
|
67 | };
|
68 | |
69 | |
70 | class OutputPin : public Pin { /** an output pin */ |
71 | public:
|
72 | OutputPin(_Register ddr, _Register port, |
73 | _Register pin, uint8_t num): |
74 | /** constructor @params see Pin::Pin() */
|
75 | Pin(ddr, port, pin, num) { |
76 | *this->ddr |= (1 << this->num); |
77 | }
|
78 | |
79 | /** set output high */
|
80 | OutputPin& setHigh(void) { /** set pin high */ |
81 | *this->port |= (1 << this->num); return *this; }; |
82 | |
83 | /** set output low */
|
84 | OutputPin& setLow(void) { /** set pin low */ |
85 | *this->port &= ~(1 << this->num); return *this; }; |
86 | |
87 | /** toggle output */
|
88 | OutputPin& toggle(void) { /** toggle pin */ |
89 | *this->pin |= (1 << this->num); return *this; }; |
90 | |
91 | /** assignment operator
|
92 | * @param parm if true: set pin high, else set pin low
|
93 | */
|
94 | void operator=(bool const &parm) { |
95 | if(parm) { |
96 | this->setHigh(); |
97 | } else { |
98 | this->setLow(); |
99 | }
|
100 | }
|
101 | };
|
102 | |
103 | |
104 | /**
|
105 | * deriving this from InputPin and OutputPin by virtually
|
106 | * inheriting from Pin bloats code so we better duplicate
|
107 | */
|
108 | class InoutPin: public Pin { |
109 | public:
|
110 | /** constructor
|
111 | * @params see Pin::Pin()
|
112 | */
|
113 | InoutPin(_Register ddr, _Register port, _Register pin, uint8_t num): |
114 | Pin(ddr, port, pin, num) |
115 | {}
|
116 | |
117 | /** set pin as input pin */
|
118 | InoutPin& setInput(void) { *this->ddr &= ~(1 << this->num); return *this; } |
119 | |
120 | /** set pin as output pin */
|
121 | InoutPin& setOutput(void) { *this->ddr |= (1 << this->num); return *this; } |
122 | |
123 | // input functions (see InputPin)
|
124 | int isHigh(void) { return (*this->pin & (1 << this->num)); }; |
125 | int isLow(void) { return !((*this->pin & (1 << this->num))); }; |
126 | |
127 | // output functions (see OutputPin)
|
128 | InoutPin setHigh(void) { *this->port |= (1 << this->num); return *this; }; |
129 | InoutPin setLow(void) { *this->port &= ~(1 << this->num); return *this; }; |
130 | void operator=(bool const &parm) { |
131 | if(parm) { |
132 | this->setHigh(); |
133 | } else { |
134 | this->setLow(); |
135 | }
|
136 | }
|
137 | };
|
138 | |
139 | #endif // _PIN_HPP
|
Liebe Grüße, Karl
Hallo Scelumbro, Scelumbro schrieb: > Es gibt bereits eine weitverbreitete, erfolgreiche und halbwegs > Plattform unabhängige C++ Library für uC: Arduino. > Und die zeigt meines Erachtens bestens warum so ein Projekt für eine > breite Zielgruppe fast unmöglich ist: > - Für Anfänger kann es nicht einfach und idiotensicher genug sein, > Performance ist Zweitrangig > - ASM Programmierer wollen sowieso jedes Bit selbst kontrollieren, und > bekommen einen Herzkasper für jede unnötige Anweisung die der Compiler > generiert > - Erfahrene uC Programmierer würden schon gerne jedes noch so spezielle > Feature ihres ZieluC ausnutzen und genau darüber wissen, was das > Framework so in den Registern anstellt. > - Reine C Programmierer können sich mit einfacher Objektorientierung > noch anfreundne, aber spitze Klammern kennen die nur aus Operatoren für > Vergleich und Shift. > - Gute C++ Programmierer könnten sicherlich ein Lib aufstellen, die mit > so Sachen wie Objektorientierung, Templates, Metaprogrammierung einen > hocheffizienten Code erstellt, aber für keine der vorgenannten Gruppen > noch zugänglich wäre, weil sie in spitzen Klammern und völlig > unverständlichen Compilerfehlermeldungen ertrinken. > (Das ist jetzt alles etwas zugespitzt) Das ist alles sehr richtig, was Du schreibst, vielen Dank. Aus den von Dir so treffend formulierten Gründen würde ich eine Bibliothek bevorzugen, die auf der AVR-Libc aufbaut -- damit die erfahrenen uC-Entwickler einerseits immer noch alle Features ihres Ziel-uC ausnutzen können, wo das notwendig ist, andererseits aber ansonsten den Komfort von C++ nutzen können. Zudem soll das Ganze idealerweise schlank und effizient, trotzdem auch noch für weniger erfahrene C++-Entwickler zugänglich bleiben. Höre ich da jemanden "eierlegende Wollmilchsau" sagen? Mag sein. Aber die von mir eben gepostete Pin-Bibliothek zeigt IMHO, daß die Ziele zumindest für diese triviale Funktionalität durchaus vereinbar sind. Ich persönlich bin gespannt, ob das auch bei nichttrivialen Funktionalitäten so bleibt. > Arduino hat sich deswegen einzig und allein auf die erste Gruppe > konzentriert und sehr großen Erfolg gehabt. Und alle anderen Gruppen > schimpfen über ineffizienten, langsamen, großen Code der viele > Besonderheiten der Zielplattform versteckt oder überhaupt nicht > zugänglich macht. ...und kreiden den wegen der Anfängerfreundlichkeit entstandenen Bloat dann leider C++ an. Liebe Grüße, Karl
>Es gibt bereits eine weitverbreitete, erfolgreiche und halbwegs >Plattform unabhängige C++ Library für uC: Arduino. >Und die zeigt meines Erachtens bestens warum so ein Projekt für eine >breite Zielgruppe fast unmöglich ist: >- Für Anfänger kann es nicht einfach und idiotensicher genug sein, >Performance ist Zweitrangig Die Arduino LIB ist gar nicht so schlecht. Wie ich schon weiter oben erwähnt habe, kann man damit den selben Code auf AVR und ARM laufen lassen. Im Gegensatz zu diesem Thread hier haben die Arduino Entwickler irgendwann angefangen, Code auszuliefern, obwohl er noch nicht perfekt war. Wie aber in diesem Thread gezeigt, kann man bei geschickter C++ Programmierung auch die Pin-Zugriffsfunktionen wie digitalWrite hoch performant machen. Man könnte die Erkenntnisse dieses Threads also durchaus verwenden, um die Arduino LIB zu optimieren. Es macht dabei durchaus Sinn, bei den gleichen Funktionsnamen zu bleiben und kein neuen zu erfinden. Das Netz ist voll von HELP-Files und Beispielen für die Befehle und Programme. Das ist ein Dokumentations und Erklärungsaufwand, der mit einer neuen, anderen Lib nur sehr schwer zu verbessern ist. Man darf die LIB auch durchaus erweitern, wenn einem die Funktionen nicht reichen. Auch dafür gibt es viele Beispiele im Netz.
Es gibt von der iX-Developer (Heise) ein Sonderheft "Embedded Software". Dort ein Artikel "Schlanke Embedded-Entwicklung mit Small-C++". Der beschreibt Anhand eines kleinen Beispiels für AVR, wie man trotz C++ den Code klein hält. Also es wird an einem kleinem Beispiel von C zu C++ entwickelt und gezeigt, wie sich die Codegröße ändert. Vielleicht interessiert es ja den TO oder auch sonst jemand. Gruß Joachim
:
Bearbeitet durch User
Karl Käfer schrieb: > Um das Ganze womöglich wieder in etwas geordnetere Bahnen zu lenken -- > und nicht zuletzt, weil konkreter Code IMHO immer noch die beste > Grundlage für diese Diskussion ist -- hier meine aktuelle Pin.hpp. Danke! Ich verstehe aber noch nicht, wie man deine Klassen weiter nutzen soll. Folgendes Beispiel adaptiert von Beitrag "Re: C++ auf einem MC, wie geht das?":
1 | class LCD |
2 | { |
3 | private: |
4 | Pin *data; |
5 | Pin *E; |
6 | Pin *RS; |
7 | Pin *RW; |
8 | public: |
9 | LCD(Pin *d,Pin *E,Pin *RS,Pin *RW) : data(d), E(E), RS(RS), RW(RW) {}; |
10 | void DataOut(uint8_t value) |
11 | { |
12 | data.??? |
13 | } |
14 | void Enable(uint8_t value) |
15 | { |
16 | if (value) |
17 | E.??? |
18 | else |
19 | E.??? |
20 | } |
21 | }; |
22 | |
23 | OutputPin data ( PINDEF(B, 0) ); |
24 | OutputPin e ( PINDEF(B, 1) ); |
25 | OutputPin rs ( PINDEF(B, 2) ); |
26 | OutputPin rw ( PINDEF(B, 3) ); |
27 | |
28 | LCD LCD((Pin*)&data,(Pin*)&e,(Pin*)&rs,(Pin*)&rw); |
Deine Pin Klasse ist doch leer. Welche Methoden soll LCD da denn benutzen? Also um klar zu sein: Du hast NIE eine automatische virtuelle Vererbung. Du must schon explizit `virtual` hinschreiben damit der Compiler das auch macht. Nur dann weiß der Compiler was er überhaupt aufrufen kann und soll.
DualZähler schrieb: > Deine Pin Klasse ist doch leer. Welche Methoden soll LCD da denn > benutzen? > Also um klar zu sein: Du hast NIE eine automatische virtuelle Vererbung. > Du must schon explizit `virtual` hinschreiben damit der Compiler das > auch macht. Ich denke, wir sind uns einig, dass zumindest in Hardware-nahen Bibliotheken (Pin, UART, Device-Treiber wie z.B. für NRF24L01+, ...) VMTs ('virtual') ein "no-go" sind. Mit einer "Objekt-Komposition" (siehe "Quadrat vs. Rechteck"^^) macht man das dann z.B. so:
1 | class MeinPinMitMethoden { |
2 | InputPin pin; |
3 | OutputPin led; |
4 | Timer timer; |
5 | // Weitere Member (Methoden, Zustandsautomaten, ...)
|
6 | };
|
Oder? @Hans-Georg: Ich meinte, dass man irgendwie in dieser Richtung weiter denken müsste und nicht mit "led3 = timedBtn" in der MainLoop. Wozu soll denn andauernd diese Zuweisung wiederholt werden? Es muss eine Art "Ereignis" ausgewertet werden, wenn ein Timer abläuft, eine Taste gedrückt oder losgelassen wird. Also ein Callback, Delegat oder Event-Handler, ... aufgerufen werden. Torsten C. schrieb: > Wenn das mit Objekt-Komposition^^ klappt, würde ich eine "fest > verdrahtete" und eine dynamische Variante anlegen Jo, geht beides, der Compiler hatte nix zu meckern: festverdrahtet wie oben oder:
1 | class MeinPinMitMethoden { |
2 | InputPin * const pin; // festverdrahtet |
3 | OutputPin * const led; // festverdrahtet |
4 | Timer * const timer; // festverdrahtet |
5 | // Weitere Member (Methoden, Zustandsautomaten, ...)
|
6 | };
|
dynamisch:
1 | class MeinPinMitMethoden { |
2 | InputPin * pin; // dynamisch |
3 | OutputPin * led; // dynamisch |
4 | Timer * timer; // dynamisch |
5 | // Weitere Member (Methoden, Zustandsautomaten, ...)
|
6 | };
|
:
Bearbeitet durch User
Karl Käfer schrieb: > Um das Ganze womöglich wieder in etwas geordnetere Bahnen zu lenken -- > und nicht zuletzt, weil konkreter Code IMHO immer noch die beste > Grundlage für diese Diskussion ist ...: Um mal eine Alternative zu zeigen, die schon länger bei mir rumliegt:
1 | #include <avr/io.h> |
2 | |
3 | #define GPIOB_BASE 0x23
|
4 | #define GPIOC_BASE 0x26
|
5 | #define GPIOD_BASE 0x2B
|
6 | #define GPIOE_BASE 0x2C
|
7 | #define GPIOF_BASE 0x2F
|
8 | |
9 | typedef struct |
10 | {
|
11 | uint8_t pin; |
12 | uint8_t ddr; |
13 | uint8_t port; |
14 | |
15 | } GPIO_t; |
16 | |
17 | |
18 | |
19 | template <uint8_t baseaddr> struct AVRgpioBank |
20 | {
|
21 | GPIO_t * reg; |
22 | |
23 | AVRgpioBank() |
24 | {
|
25 | reg = reinterpret_cast<GPIO_t *>(baseaddr); |
26 | |
27 | }
|
28 | };
|
29 | |
30 | |
31 | struct gpioPin |
32 | {
|
33 | void setDir(const bool value) {} |
34 | void getValue() const {} |
35 | void setValue(const bool value) {} |
36 | bool operator =(const bool &value) {} |
37 | bool operator == (const bool &value) {} |
38 | };
|
39 | |
40 | //Template class for one single GPIO pin
|
41 | template<uint8_t baseaddr, uint8_t num> struct AVRgpioPin : public gpioPin |
42 | {
|
43 | AVRgpioBank<baseaddr> port; |
44 | |
45 | void setDir(const bool value) |
46 | {
|
47 | if(value) |
48 | {
|
49 | port.reg ->ddr |= (1<<num); |
50 | }
|
51 | else
|
52 | {
|
53 | port.reg ->ddr &= ~(1<<num); |
54 | }
|
55 | }
|
56 | bool get() const |
57 | {
|
58 | return ((port.reg ->pin) && (1<<num)); |
59 | }
|
60 | void set(const bool value) |
61 | {
|
62 | if(value) |
63 | {
|
64 | port.reg ->port |= (1<<num); |
65 | }
|
66 | else
|
67 | {
|
68 | port.reg ->port &= ~(1<<num); |
69 | }
|
70 | }
|
71 | |
72 | bool operator =(const bool &value) |
73 | {
|
74 | set(value); |
75 | return value; |
76 | }
|
77 | bool operator == (const bool &value) |
78 | {
|
79 | return (get() == value); |
80 | |
81 | }
|
82 | |
83 | |
84 | int main(void) |
85 | {
|
86 | AVRgpioPin<GPIOC_BASE, 0> led; |
87 | led = true; |
88 | while(1) |
89 | {
|
90 | //TODO:: Please write your application code
|
91 | }
|
92 | }
|
Die main() kompiliert, wird zu zwei sbi und einem Sprung für die while(1). Letztendlich mag das für GPIOs ganz gut funktionieren. Wie aber bildet man plattformübergreifend Timer, serielle Schnittstellen und insbesondere die ISRs ab? Überhaupt die zusammenarbeit von ISR mit C++ bereitet mir noch Kopfzerbrechen.
Hallo Torsten, ich geh jeztz mal davon aus, das wie beide der Meinung sind, der LED Zustand soll sich jedesmal ändern wen sich an einer Taste was ändert. Da hast du praktisch mehrere Möglichkeiten, eine davon ist in der Hauptschleife die Taste zu pollen, genau das habe ich gemacht. Du könntest für die Buttons auch z.B. Pin Change Interrupt nehmen aber wie erfährt dann die jeweilige Led davon das sich etwas geändert hat. Da musst du dann Listen pflegen welche LED hängt an welchem Button. Meine "Design Entscheidung" an dieser Stelle war: Led schau doch selber nach ob sich was geändert hat ;) Und ich gebe ihr in der Hauptschleife genug Möglichkeit das auch zu tun. Was man dem geposteten (Pseudo) Code nicht direkt ansieht ist folgendes: Die SuperLed hat überladene Zuweisungsoperatoren(=), für jeden Button(Typ) und der ruft GetState() vom jeweiligen Button(Objekt) auf. Das sollte keine "Vorschrift" sein wie man etwas implementiert sondern ich habe nur Peters Pseudo Code etwas umgeschrieben ... ;) Peter Dannegger schrieb: > In C++ könnte man Zuweisungen nehmen, wenn man nur wüßte, wie man das > implementiert. >
1 | > LED0 = KEY0.state; // an solange Taste gedrückt |
2 | > LED1.toggle = KEY1.press // wechsel bei jeder Drückflanke |
3 | > LED2.off = KEY2.press_short; // kurz drücken - aus |
4 | > LED2.on = KEY2.press_long; // lang drücken - an |
5 | >
|
>
Also Peter, wenn du noch mit liest .. es geht ;)
Torsten C. schrieb: > nicht mit "led3 = timedBtn" in der MainLoop. Wozu soll denn andauernd > diese Zuweisung wiederholt werden? > > Es muss eine Art "Ereignis" ausgewertet werden Diese Designentscheidung sollte dem User überlassen werden. Die lib kann ja templates für events und eventQueues bereirstellen, und die verwendung bleibt dem user überlassen. Gleiches denke ich auch über ALLE ISRs: Templates und Makros für default ISRs, welche der nutzer nutzen könnte, aber nicht muss. Man macht dazu dan ein "best practice guid". Darin empfielt man dann das anlegen Conroller und Layoutspezifischer headerfiles, mit controllerunspezifischen .cpp files:
1 | // Platine1Conf.hpp
|
2 | #include<ucpp> // includiere lib |
3 | namespace hardware { |
4 | namespace LED { |
5 | static const ucpp::Port<0>::Pin<0> status; // declariere status led |
6 | static const ucpp::Port<0>::Pin<1> error; // declariere error led |
7 | }
|
8 | inline void init(){ |
9 | LED::status.setOutput(); |
10 | LED::error.setOutput(); |
11 | }
|
12 | }
|
13 | |
14 | // Main.cpp
|
15 | #include CONFIG_FILE // includire config abhängig von macro
|
16 | |
17 | int main(){ |
18 | Hardware::init(); |
19 | while( true ){ |
20 | Hardware::LED::status.toggle(); |
21 | delay_ms(500); |
22 | }
|
23 | }
|
24 | |
25 | // compilieren
|
26 | avr-gcc -mcu=xyz -DCONFIG_FILE="Platine1Conf.hpp" ... |
Scelumbro schrieb: > Überhaupt die zusammenarbeit von ISR mit C++ bereitet mir noch > Kopfzerbrechen. Auf einem STM32F4 Discovery Board habe ich das so gelöst:
1 | (...)
|
2 | static dma::DmaChannel ada_dmaChannel(adc_dmaStream, 0); |
3 | |
4 | static devices::AdcViaSTM32F4_Adc1 adc(rcc, ada_dmaChannel); |
5 | |
6 | (...)
|
7 | |
8 | #if defined(__cplusplus)
|
9 | extern "C" { |
10 | #endif /* defined (__cplusplus) */ |
11 | |
12 | void
|
13 | ADC_IRQHandler(void) { |
14 | adc.handleIrq(); |
15 | }
|
16 | |
17 | void
|
18 | DMA2_Stream0_IRQHandler(void) { |
19 | adc_dmaStream.handleIrq(); |
20 | }
|
21 | |
22 | #if defined(__cplusplus)
|
23 | } /* extern "C" */ |
24 | #endif /* defined (__cplusplus) */ |
Also im Prinzip nicht anders als es in C gemacht würde: Die betroffenen Methoden aus dem Interrupt-Handler direkt aufrufen. Der Unterschied ist halt, dass man dazu die betreffenden Objekte erreichbar machen muss -- hier eben als globale Objekte. In C++ bedeutet das allerdings, dass man sich um das Initialisieren der statischen Objekte -- bzw. die Reihenfolge davon -- Gedanken machen muss. Ich habe das Problem umschifft, in dem ich eben nur in einer einzigen .cpp Datei im Projekt statische Objekte anlege. Eine Zeit lang habe ich darüber nachgedacht, so etwas wie einen Interrupt Handler zu modellieren. Das hätte dann bedeutet, dass sich die Objekte, die den Interrupt behandeln wollen, beim Handler hätten registrieren müssen. Ich habe keine Lösung gefunden, wie man das ohne virtual Deklarationen hin kriegt. Und meinen Anspruch, dass sich die am Interrupt interessierten Objekte schon zur Compile-Zeit auf den Interrupt "registrieren" müssen, den wollte ich nicht aufgeben.
Und jetzt noch der unvollständige, unausgegore und wahrscheinlich fehlerhafter Code aber ich denke das Prinzip der Operator Überladung wird für nicht C++ Spezialisten auch klar und darum gehts ... typedef enum { off, on, toggle, fast_blink, normal_blink, slow_blink } SUPER_LED_STATE; Und nun die verschiedenen überladenen Operatoren bool SuperLed::operator = (const bool value ) { if( value) led_State = on; else led_State = off; return value; } Damit geht: led = false/true; und: led1 = led2 = led3 = false/true; // und das gleiche für SUPER_LED_STATE EXT_LED_STATE SuperLed::operator = (const SUPER_LED_STATE state ) { led_State = state; return state; } Damit geht: led = fast_blink; und: led1 = led2 = led3 = slow_blink; const DigitalInput& SuperLed::operator = ( const DigitalInput& inp ) { bool value = inp.GetInput(); // liefert nur true/false if( value) led_State = on; else led_State = off; return inp; } // Ich denke ab hier macht Verkettung keinen Sinn mehr deshalb void // Wenn man verkettung möchte muss man die Referenz auf den Button zurückgeben void SuperLed::operator = ( const SimpleButton& sib ) { // liefert nur true/false bool value = inp.GetInput(); if( value) led_State = on; else led_State = off; } void SuperLed::operator = ( const EdgeButton& edb ) { // liefert no_edge_detect, edge_detect EDGE_BTN_STATE value = edb.GetInput(); if( edge_detect){ // toggle habe ich als einen eigenen state definiert, damit wenn der blink callback noch aktiv ist er nicht ausgewertet wird led_state = toggle; this.Toggle(); // jetz müssen wir aber dem Edge Button irgendwie mitteilen das wir die Flanke ausgewertet haben // er sich auf no_edge_detect setzen soll und warten bis der Benutzer die Taste neu gedrückt hat. edb.Release(); } } void SuperLed::operator = ( const TimedButton& tib ) { TIMED_BTN_STATE value = tib.GetState(); switch (value) { case not_pressed : // nix machen; break; case short_pressed : led_state = off; break; case normal_pressed : led_state = fast_blink; break; case long_pressed : led_state = slow_blink; break; } }
:
Bearbeitet durch User
Ich würde so weit gehen und die LED, den Taster und die Verbindung zwischen beiden in separate Klassen einbauen. Die LED ist ein GPIO_output, der Taster ein GPIO_input und beide werden über einen "Logikbaustein" (der nichts mit der Hardware zu tun hat) verbunden. Soll das Ganze ein Blinklicht werden, dann muss sich eben dieser Logikbaustein darum kümmern, dass er in jeder x-ten Timer-ISR aufgerufen wird und entsprechend die LED blinken lässt. Damit er das tun kann, wird im bei der Erstellung der Output, Input und der entsprechende Timer übergeben und die Klasse bzw der Compiler kümmert sich um den Rest.
Achim schrieb: > Ich würde so weit gehen und die LED, den Taster und die Verbindung > zwischen beiden in separate Klassen einbauen. > > Die LED ist ein GPIO_output, der Taster ein GPIO_input und beide werden > über einen "Logikbaustein" (der nichts mit der Hardware zu tun hat) > verbunden. > > Soll das Ganze ein Blinklicht werden, dann muss sich eben dieser > Logikbaustein darum kümmern, dass er in jeder x-ten Timer-ISR aufgerufen > wird und entsprechend die LED blinken lässt. > > Damit er das tun kann, wird im bei der Erstellung der Output, Input und > der entsprechende Timer übergeben und die Klasse bzw der Compiler > kümmert sich um den Rest. Hallo Achim, das kann jeder machen wie er will. Und du siehst ja wie die Meinungen hier manchmal auseinander gehen. Es gibt hier kein falsch oder richtig. Aber wenn du eine Lib für die Allgemeinheit schreiben willst solltest du das wie Daniel schon schrieb dem Anwender deiner Lib überlassen.
Achim schrieb: > Ich würde so weit gehen und die LED, den Taster und die Verbindung > zwischen beiden in separate Klassen einbauen. … > Soll das Ganze ein Blinklicht werden, dann muss sich eben dieser > Logikbaustein darum kümmern, dass er in jeder x-ten Timer-ISR aufgerufen > wird Den gleichen Gedanken hatte ich bei MeinPinMitMethoden^^. Hans-Georg Lehnard schrieb: > wenn du eine Lib für die Allgemeinheit schreiben willst solltest du > das wie Daniel schon schrieb dem Anwender deiner Lib überlassen. Bei MeinPinMitMethoden^^ hatte ich das nicht explitit erwähnt: Genau so war das gedacht. Es war ein Versuch einer Antwort auf das, was DualZähler schrieb: > Deine Pin Klasse ist doch leer. Welche Methoden soll LCD da denn > benutzen? Die Antwort wäre: Die Klasse stellt nur Basis-Methoden zur Verfügung. Das "drumherum" passiert dann durch abgeleitete Klassen (vorzugsweise ohne 'virtual') oder Kompositionen. Nicht nur, dass jeder Progammierer "seinen eigenen Dickkopf" hat (das ist gar nicht böse gemeint, jeder hat halt so seine Erfahrungen). Auch wenn man Objektmodelle verschiedener Projekte mehreren erfahrenen Programmierern zum Review gibt, wird ein Konsesns von der Applikation und den individuellen Randbedingungen abhängig sein, also nicht unbedingt für alle Projekte gleich aussehen.
:
Bearbeitet durch User
chris_ schrieb: > Man könnte die Erkenntnisse dieses Threads also > durchaus verwenden, um die Arduino LIB zu optimieren. Es macht dabei > durchaus Sinn, bei den gleichen Funktionsnamen zu bleiben und kein neuen > zu erfinden. Den Gedanken finde ich sehr attaktiv, … 1. weil man damit - wie gesagt - bereits den selben Code auf AVR und ARM laufen lassen kann und 2. weil vielen die Benennungen der Klassen und Methoden geläufig sind. Ich habe noch nie mit der Arduino LIB gearbeitet, also bisher alls "zu Fuß" selbst gemacht. @All, die die Arduino LIBs kennen: Könnte man den "riesigen Source- und Codebloat"^^ sinngemäß mit #ifdef DEBUG / #else / #endif ausblenden? Oder ist das gesamte Objektmodell zu unflexibel aufgesetzt?
:
Bearbeitet durch User
Hallo DualZähler, DualZähler schrieb: > Ich verstehe aber noch nicht, wie man deine Klassen weiter nutzen soll. > Folgendes Beispiel adaptiert von > Beitrag "Re: C++ auf einem MC, wie geht das?" Der Code in dem verlinkten Beitrag ist von Ralf und hat mit meinem nichts zu tun. > Deine Pin Klasse ist doch leer. Welche Methoden soll LCD da denn > benutzen? Aber nein, meine Klasse "Pin" ist keineswegs leer, sondern sie enthält die vier Datenfelder "ddr", "port", "pin" und "num", die übrigens bei mir nur "protected" statt wie bei Ralf "private" sind. Die Klasse "Pin" ist deswegen bei mir nur eine Basisklasse für die Klassen "InputPin", "OutputPin" und "InoutPin" mit entsprechenden Funktionen. > Also um klar zu sein: Du hast NIE eine automatische virtuelle Vererbung. > Du must schon explizit `virtual` hinschreiben damit der Compiler das > auch macht. Virtuelle Vererbung würde ich gerne vermeiden, weil der Compiler dann mit hoher Wahrscheinlichkeit virtual method tables (VMTs) anlegt, die jedoch relativ viel Speicher kosten. > Nur dann weiß der Compiler was er überhaupt aufrufen kann und soll. Wie gesagt: die Kind-Klassen "xxxPin" enthalten entsprechende Funktionen, mit denen Du einen Eingabe-Pin lesen und einen Ausgabe-Pin high oder low setzen kannst. Ich habe Dir meine Datei "main.cpp" angehängt, die ein in diesem Thread gewünschtes Beispiel implementiert und meine Klassen nutzt. Liebe Grüße, Karl
Karl Käfer schrieb: > Virtuelle Vererbung würde ich gerne vermeiden, ich auch :-) > weil der Compiler dann > mit hoher Wahrscheinlichkeit virtual method tables (VMTs) anlegt, die > jedoch relativ viel Speicher kosten. Bei meinen Versuchen immer. Deshalb sollte das ein Beispiel sein, wie man es nicht macht. Es werden nicht nur die 'benötigten' Funktionen eingetragen, sondern auch für die nicht benutzten, für die auch noch Code erzeugt wird. Obwohl... Man könnte mal sehen, was der LTO dazu sagt... Edit: ... der schlägt bei meinem Test noch zwei Bytes drauf.
:
Bearbeitet durch User
Hallo, weitere Überlegungen zum Thema brachten mich auf die Idee, auf einer noch tieferen Ebene anzusetzen und zunächst einmal etwas für die komfortablere Manipulation einzelner Register zu implementieren. Denn was ich vor allem nicht mag, ist die häßliche Syntax zur Bitmanipulation in C/C++. Da kann ich ja gleich in Perl programmieren... ;-) Also habe ich zunächst einmal Template-Funktionen zur Manipulation und Abfrage von Bits in 8- und 16-Bit-Registern geschrieben, welche sich in "Reg.hpp" finden. Danach habe ich die Pin-Klassen in "Pin.hpp" an diese Register-Implementierung angepaßt. Die "main.cpp" ist unverändert, alle Dateien sind angehängt. Wenn ich diese Dateien mit "avr-g++ -mmcu=atmega32 -Os" übersetze und das Kompilat mit avr-strip(1) bearbeite, ist das Ergebnis gemäß avr-size(1) exakt 130 Byte groß. Damit jetzt nicht gleich wieder so ein besonders kluger Mensch aufpoppt und uns erklärt, daß C++ ein riesiger Codebloat und furchtbar ineffizient wäre, habe ich dieselbe Funktionalität noch einmal in C umgesetzt. Und siehe da: mit denselben Compilereinstellungen und avr-strip(1) bearbeitet, hat der resultierende Code einen Umfang von 148 Byte, unabhängig davon, ob ich den C-Code mit avr-g++ oder avr-gcc übersetze. Kurz gesagt: die C++-Implementierung ist über 12% kleiner als die in C. Wer das selbst überprüfen möchte, findet auch die "main.c" im Anhang. Liebe Grüße, Karl
@Karl Käfer
1 | template <typename T, typename U> bool _get(T obj, U bit) { return *obj & (1 << bit); } |
kann das sein, dass hier noch das return rein muss?
Karl Käfer schrieb: > > Damit jetzt nicht gleich wieder so ein besonders kluger Mensch aufpoppt > und uns erklärt, daß C++ ein riesiger Codebloat und furchtbar > ineffizient wäre, habe ich dieselbe Funktionalität noch einmal in C > umgesetzt. Und siehe da: mit denselben Compilereinstellungen und > avr-strip(1) bearbeitet, hat der resultierende Code einen Umfang von 148 > Byte, unabhängig davon, ob ich den C-Code mit avr-g++ oder avr-gcc > übersetze. > Hallo Karl, ich lasse normal dem Compiler ein leeres main übersetzen und schau mir an wieviel Code erzeugt wird. Das ist von MC Typ abhängig, liegt hauptsächlich an der ISR Tabelle die mal grösser oder kleiner ist und ob static variable initialisiert werden. Das wird auch bei normalem C so gemacht. Dann include ich meine templates und dann sollte sich der code nicht vergrössern sonst schleppt die Implementation irgendwas mit. Zum Schluss lege ich in main variable an die meine Templates instanziieren und vergleiche die code grösse mit dem leeren main. Wenn du noch kleineren code haben willst, musst du den Startup code auch noch selber schreiben. Das ist aber auch kein Hexenwerk.
@Karl Käfer Was mir die größten Sorgen macht, dass man sich zwar bei den Minimalbeispielen an der geringen Codegröße erfreuen kann. Aber der Bereich 'hardware abstraction' ist doch im richtigen Leben viel umfangreicher und wird deshalb schön übersichtlich in Module verteilt. Ich hab' das mal auseinandergepflückt (siehe Anhang). Für eine ordentliche Code-Größe hat es sich dann mit 'AVR-Studio aufmachen - Projekt anlegen - Dateien einfügen - Compilern' erledigt!
Ralf G. schrieb: > @Karl Käfer >
1 | > template <typename T, typename U> bool _get(T obj, U bit) { return *obj |
2 | > & (1 << bit); } |
3 | >
|
> kann das sein, dass hier noch das return rein muss?
Noch eine Anmerkung zu dieser Schreibweise (1 << bit).
Das hat Atmel so eingeführt und jeder macht es.
Ich finde es hässlich, und wenn man mehrere Bits maskiert wird es auch
nicht schöner .. aber das ist meine private Meinung dazu.
Aber ..
wenn du in deiner funktion shiftest kannst du ihr keine maske wo mehrere
bits gleichzeitig gesetzt sind mehr übergeben. Vorschlag: direkt die
bitmaske und keine pin nummer übergeben.
und noch was lustiges ..
enum {
bit0 = 1,
bit1 = 1<<1,
...
bit30 = 1<<30, // bis hierhin alles OK
bit31 = 1<<31 // Compiler sagt Fehler !!
und warum ? weil enums 32Bit aber signed und nicht unsigned sind und du
multiplizierts dann bei dem 31sten schieben nicht mehr mit 2 sondern
schiebst die 1 in das Vorzeichen. Übrigends auch Array indices sind
signed und können negativ sein.
Bei den C++11 enums kannst du den darunterliegenden Datentyp uint32_t
angeben und dann geht es wieder ...
Hans-Georg Lehnard schrieb: > Noch eine Anmerkung zu dieser Schreibweise (1 << bit). > Das hat Atmel so eingeführt und jeder macht es. Das glaub ich jetzt wieder nicht, dass Atmel das erfunden hat... > Ich finde es hässlich, und wenn man mehrere Bits maskiert wird es auch > nicht schöner .. Wie lautet dein Gegenvorschlag? (in C, ohne ++) Ich finde das sehr praktisch, weil man in einer zeile alle Bits angeben kann, und sofort erkennt welches Bit 1 und welches 0 ist:
1 | TCCR0B = (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00); |
Wie würdest du das gerne "schöner" codieren? (in C, ohne ++)
Michael Reinelt schrieb: > Wie würdest du das gerne "schöner" codieren? Über Geschmack kann man nicht streiten. Andere (lassen wir Atmel/STM/... mal weg), definieren z.B. sinngemäß:
1 | static const int FOC0A = (1<<0); |
2 | static const int FOC0B = (1<<1); |
3 | static const int WGM00 = (1<<2); |
4 | static const int WGM01 = (1<<3); |
5 | static const int WGM02 = (1<<4); |
6 | static const int CS00 = (1<<5); |
7 | static const int CS01 = (1<<6); |
8 | static const int CS02 = (1<<7); |
... oder fassen mehrere Bits zusammen. Z.B. in den MSP430-Bibliotheken ist das so.
:
Bearbeitet durch User
Michael Reinelt schrieb: > Ich finde das sehr praktisch, weil man in einer zeile alle Bits angeben > kann, und sofort erkennt welches Bit 1 und welches 0 ist: Ich find's sogar schön, Bits zusammenzufassen...
1 | TCCR0B = (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0b010 << CS00); // 3Bits für CS0n |
Torsten C. schrieb: > oder fassen mehrere Bits zusammen. Z.B. in den MSP430-Bibliotheken Ralf G. schrieb: > Ich find's sogar schön, Bits zusammenzufassen... Hier ein Beispiel: https://github.com/noccy80/mspdev/blob/master/reference/MSP430ware/deprecated/CCS/msp430x552x.h
Torsten C. schrieb: > Michael Reinelt schrieb: >> Wie würdest du das gerne "schöner" codieren? > > Über Geschmack kann man nicht streiten. Andere (lassen wir Atmel/STM/... > mal weg), definieren z.B. sinngemäß: > > [c]static const int FOC0A = (1<<0); > static const int FOC0B = (1<<1); > static const int WGM00 = (1<<2); ... > ... oder fassen mehrere Bits zusammen. Z.B. in den MSP430-Bibliotheken > ist das so. Beantwortet aber meine Frage nicht: Wie schreibst du, dass du WGM00 auf 0 setzte, WGM01 auf 1 und WGM02 auf 0? So dass auch erkennbar ist dass WGM00 und WGM02 auf 0?
Michael Reinelt schrieb: > Hans-Georg Lehnard schrieb: >> Noch eine Anmerkung zu dieser Schreibweise (1 << bit). >> Das hat Atmel so eingeführt und jeder macht es. > Das glaub ich jetzt wieder nicht, dass Atmel das erfunden hat... > >> Ich finde es hässlich, und wenn man mehrere Bits maskiert wird es auch >> nicht schöner .. > > Wie würdest du das gerne "schöner" codieren? (in C, ohne ++) ich mach das gerne so .. typedef enum { PA0 = 0x01u, PA1 = 0x02u, PA2 = 0x04u, PA3 = 0x08u, PA4 = 0x10u, PA5 = 0x20u, PA6 = 0x40u, PA7 = 0x80u }PORTA_BITS; typedef enum { PB0 = 0x01u, PB1 = 0x02u, PB2 = 0x04u, PB3 = 0x08u, PB4 = 0x10u, PB5 = 0x20u, PB6 = 0x40u, PB7 = 0x80u }PORTB_BITS; Dann ist dem Compiler auch klar, das ich unsigned meine ... (1<<5) ist für den Compiler signed int, 0x20u nicht. Es liegt vielleicht daran, das ich mit Binärzahlen aufgewachsen bin und wenn ich mit Leuten von der PD11 diskutierte immer im Kopf zwischen oktal und hex hin und her umrechnen musste aber das ist wie gesagt reine Geschmacksache; Und wenn ich das in C++ als Datentypen für Templates nehme. DigitalInput<PORT_A, PB> gibt dann ein Compiler Fehler.
:
Bearbeitet durch User
Hans-Georg Lehnard schrieb: > (1<<5) ist für den Compiler signed int, 0x20u nicht. (1U << 5) aber auch nicht.
Hans-Georg Lehnard schrieb: > ich mach das gerne so .. Beantwortet auch nciht meine Frage. Wie codierst du mein obiges Beispiel? So dass sofort erkennbar ist, dass ein gewisses bit gesetzt wird, aber auf 0?
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > ich lasse normal dem Compiler ein leeres main übersetzen und schau mir > an wieviel Code erzeugt wird. Schon klar, aber ich bin faul und mir geht es aber nur um eine schnelle Vergleichbarkeit. Darum übersetze ich die verschiedenen Quellcodes einfach immer für dasselbe Ziel und vergleiche die Größen der Kompilate, das muß für einfache Betrachtungen genügen. > Wenn du noch kleineren code haben willst, musst du den Startup code auch > noch selber schreiben. Das ist aber auch kein Hexenwerk. Das ist kein Hexenwerk, aber ich bin wie gesagt faul und mir geht es hier ausschließlich um den Vergleich verschiedener Quellcodes. Darum gehe ich einfach davon aus, daß der Startcode für dasselbe Ziel immer derselbe und damit immer gleich groß ist. Liebe Grüße, Karl
Michael Reinelt schrieb: > Hans-Georg Lehnard schrieb: >> ich mach das gerne so .. > > Beantwortet auch nciht meine Frage. Wie codierst du mein obiges > Beispiel? So dass sofort erkennbar ist, dass ein gewisses bit gesetzt > wird, aber auf 0? Michael, es ist ganz einfach bei << muss ich denken bei bits in hex codieren nicht .. ich will niemand überzeugen. Und bei Nullen nehm ich & und nicht | und wenn ich selber was definiere immer in positiver Logik.
Hallo Ralf, Ralf G. schrieb: > Was mir die größten Sorgen macht, dass man sich zwar bei den > Minimalbeispielen an der geringen Codegröße erfreuen kann. Aber der > Bereich 'hardware abstraction' ist doch im richtigen Leben viel > umfangreicher und wird deshalb schön übersichtlich in Module verteilt. > Ich hab' das mal auseinandergepflückt (siehe Anhang). Für eine > ordentliche Code-Größe hat es sich dann mit 'AVR-Studio aufmachen - > Projekt anlegen - Dateien einfügen - Compilern' erledigt! Das wundert mich nicht, denn der Optimizer des GCC arbeitet nun einmal nur auf den einzelnen Übersetzungseinheiten. Deswegen kann er den Code nicht mehr anständig optimieren, sobald Du ihn über mehrere Einheiten verteilst und diese einzeln übersetzt. [1] Die kleine Codegröße meiner Kompilate baut jedoch darauf, daß der Optimizer erkennt, daß es sich bei meinen Klassen in Wahrheit nur um Sammlungen von Konstanten handelt, so daß er diese elegant von dannen optimieren kann. ;-) Nun gibt es IMHO drei Gründe, Code auf verschiedene Übersetzungseinheiten zu verteilen. Erstens dient das dazu, daß bei Änderungen an einer Einheit nur diese neu übersetzt werden muß, und bei unveränderten Einheiten nur die bereits vorhandene Objektdatei dazu gelinkt werden muß. Bei größeren Projekten spart das Übersetzungs- und damit Entwicklungszeit, aber bei in der Regel sehr kleinen Mikrocontroller-Projekten hält sich der praktische Nutzen dessen wohl in Grenzen. Zweitens kann man, wenn man kommerzielle Bibliotheken entwickelt, diese als Headerdateien mit vorkompilierten Objektdateien ausliefern, also ohne mehr von seinem Quellcode offenzulegen als die Headerdateien. Das ist bei einem OpenSource-Projekt wie diesem hier wohl eher unerwünscht, denn da geht es ja gerade um die Weitergabe des Quellcodes. Und letztlich dient das drittens auch der Übersichtlichkeit, und dieses Argument sticht natürlich auch hier. Aber um dieses Ziel zu erreichen, müssen wir die Übersetzungseinheiten nicht einzeln übersetzen und dann zusammenlinken, sondern es reicht vollkommen aus, erst den Präprozessor über "#include"-Direktiven die Drecksarbeit machen zu lassen. Damit hast Du den Code übersichtlich auf mehrere Dateien verteilt, aber sie werden trotzdem zusammen als eine Übersetzungsarbeit übersetzt und optimiert. Da spielt dann wieder die überschaubare Größe von uC-Projekten hinein. Ja, ich weiß, das ist nicht die gängige Praxis aus dem Lehrbuch. Aber ich vertrete die Ansicht, daß sowohl gängige Praktiken als auch Empfehlungen aus Lehrbüchern nicht in Stein gemeißelt sind und durchaus auch schon mal ignoriert werden dürfen, wenn es, wie hier, gute Gründe dafür gibt. Liebe Grüße, Karl [1] Das ist, am Rande bemerkt, einer der Gründe dafür, daß ich bei PC-Projekten den LLVM/clang++ nutze, der über mehrere Einheiten hinweg optimieren kann, wenngleich er bei einzelnen Einheiten etwas weniger gut optimiert.
Karl Käfer schrieb: > Aber um dieses Ziel zu erreichen, > müssen wir die Übersetzungseinheiten nicht einzeln übersetzen und dann > zusammenlinken, sondern es reicht vollkommen aus, erst den Präprozessor > über "#include"-Direktiven die Drecksarbeit machen zu lassen. OOUuuh. Ob du damit durchkommst? ;-)
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Ralf G. schrieb: >> @Karl Käfer >>
1 | >> template <typename T, typename U> bool _get(T obj, U bit) { return *obj |
2 | >> & (1 << bit); } |
3 | >>
|
>> kann das sein, dass hier noch das return rein muss? > > Noch eine Anmerkung zu dieser Schreibweise (1 << bit). > > Das hat Atmel so eingeführt und jeder macht es. So sehen Bitoperationen in C nun einmal aus, da ist Atmel unschuldig. > Ich finde es hässlich, und wenn man mehrere Bits maskiert wird es auch > nicht schöner .. aber das ist meine private Meinung dazu. > > Aber .. > > wenn du in deiner funktion shiftest kannst du ihr keine maske wo mehrere > bits gleichzeitig gesetzt sind mehr übergeben. Vorschlag: direkt die > bitmaske und keine pin nummer übergeben. Die aktuelle Version meiner "Reg.hpp" und "Pin.hpp" hat -- schon aus Gründen der Wiedererkennbarkeit -- die Funktionstemplates "_set()" in "_sbi()", "_clr()" in "_cbi()" und "_get()" in "_gbi()" umbenannt, und dazu die neuen Funktion "_sms()" und "_cms()", denen man eine komplette Maske übergeben kann. Dir Funktionen "_set()" und "_get()" arbeiten nun auf dem kompletten Register. Liebe Grüße, Karl PS: Liebe Moderatoren / Betreiber, es wäre schön, wenn auch .hpp-Dateien eine Codeansicht bekommen könnten. Besten Dank!
Michael Reinelt schrieb: > Wie schreibst du, dass du WGM00 auf > 0 setzte, WGM01 auf 1 und WGM02 auf 0? So dass auch erkennbar ist dass > WGM00 und WGM02 auf 0? Entweder so wie oben gesagt, mit &=~ statt |= oder eben durch das Zusammenfassen von mehreren Bits, wie in msp430x552x.h^^:
1 | #define ADC12DIV_0 (0*0x20u) /* ADC12 Clock Divider Select: 0 */ |
2 | #define ADC12DIV_1 (1*0x20u) /* ADC12 Clock Divider Select: 1 */ |
3 | #define ADC12DIV_2 (2*0x20u) /* ADC12 Clock Divider Select: 2 */ |
4 | #define ADC12DIV_3 (3*0x20u) /* ADC12 Clock Divider Select: 3 */ |
5 | #define ADC12DIV_4 (4*0x20u) /* ADC12 Clock Divider Select: 4 */ |
6 | #define ADC12DIV_5 (5*0x20u) /* ADC12 Clock Divider Select: 5 */ |
7 | #define ADC12DIV_6 (6*0x20u) /* ADC12 Clock Divider Select: 6 */ |
8 | #define ADC12DIV_7 (7*0x20u) /* ADC12 Clock Divider Select: 7 */ |
PS: Man könnte auch noch ein "ADC12DIV_MASK" definieren, für xxx = (xxx & ~ADC12DIV_MASK) + ADC12DIV_5;
:
Bearbeitet durch User
Hallo Ralf, Ralf G. schrieb: > Karl Käfer schrieb: >> Aber um dieses Ziel zu erreichen, >> müssen wir die Übersetzungseinheiten nicht einzeln übersetzen und dann >> zusammenlinken, sondern es reicht vollkommen aus, erst den Präprozessor >> über "#include"-Direktiven die Drecksarbeit machen zu lassen. > > OOUuuh. > Ob du damit durchkommst? ;-) Werden wir sehen, ich lasse mich gerne überraschen. ;-) Denn wenn ich nicht damit durchkomme, müßte es doch bessere Argumente dagegen als meines dafür geben. Da bin ich sehr gespannt, eventuell habe ich ja etwas übersehen. Liebe Grüße, Karl
Karl Käfer schrieb: > Die kleine Codegröße meiner > Kompilate baut jedoch darauf, daß der Optimizer erkennt, daß es sich bei > meinen Klassen in Wahrheit nur um Sammlungen von Konstanten handelt, so > daß er diese elegant von dannen optimieren kann. ;-) Mal 'ne Frage: Die Lösung ist nicht schlecht, aber wenn man "const" Deklariert, müsste mit getrennten *.cpp Dateien das Gleiche bei raus kommen, oder? PS zu dieser Bitmaskenorgie: Ich finde MSP430 nicht undbedingt besser, ist halt 'ne Alternative und Geschmackssache. Ich finde Templates oder Inline-Funktionen am besten lesbar. Man müsste sie halt mit "mehrere Bits auf einmal" umsetzen.
:
Bearbeitet durch User
Karl Käfer schrieb: > der Optimizer des GCC arbeitet nun einmal > nur auf den einzelnen Übersetzungseinheiten. Meines Wissens nach stimmt das nicht, GCC kann seit ca. 5 jahren LTO (Link Time Optimization) und die macht genau das. https://gcc.gnu.org/wiki/LinkTimeOptimization
Ob er es seit 5 Jahren kann, bezweifle ich. Aber heute kann er es sehr gut. Kein Grund den Compiler zu wechsel. Schon gar nicht, wenn man AVR-Code erzeugen will.
Bastler schrieb: > Ob er es seit 5 Jahren kann, bezweifle ich. https://gcc.gnu.org/ml/gcc/2009-10/msg00060.html Nachdem das 2009 in den trunk gewandert ist...
Nachdem ich hier eine ganze Zeit mitgelesen habe und eigentlich nichts klares zu erkennen ist, finde ich das alles mittlerweile eher abschreckend mit C++ auf einem µC zu arbeiten. Nun, in zwei bis drei Jahren möchte ich dann auch mal mit C++ anfangen und mal sehen, ob ich das dann anders sehe.
Karl Käfer schrieb: > Dahinter verbirgt sich eine Art C++, Arduino language is based on C/C++. It links against AVR Libc and allows the use of any of its functions; see its user manual for details. Wer es selber nachlesen will: http://arduino.cc/en/pmwiki.php?n=Reference/HomePage
Ok, falsch formuliert: seit wann man darauf bauen kann, daß es funktioniert. Nur um zu vermeiden, daß jemand mit einer Anno Tobak Version testet und flucht. Aber sonst sind wir ja auf einer Linie: der GCC kann das!
Michael Reinelt schrieb: > Meines Wissens nach stimmt das nicht, GCC kann seit ca. 5 jahren LTO Es ist aber auch keine Wunderwaffe. En bisschen Mitarbeit ist gefragt. Weil: Ralf G. schrieb: > Für eine > ordentliche Code-Größe hat es sich dann mit 'AVR-Studio aufmachen - > Projekt anlegen - Dateien einfügen - Compilern' erledigt! Karls Beispiel Beitrag "Re: C++ auf einem MC, wie geht das?" in Module verteilt, dem gcc mit LTO auf die Sprünge geholfen... und? ... funktioniert!! Das muss aber nicht immer so sein.
Falls ich mir mit C++ eine Lib aufbaue, würde ich die gern mit unterschiedlichen Compilern vernünftig nutzen können. Es macht offenbar viel mehr Spaß, Stundenlang über Compiler-Optimierung zu debattieren, statt einfach mit "const", "private" oder "final" dafür zu sorgen, dass der Kompiler ohne Debatte ordentlich optimiert. Zumindest nach meinem Verständnis, wie gesagt, so ähnlich wie bei 'ichbinmaldiesmaldas'^^.
:
Bearbeitet durch User
F. Fo schrieb: > Nachdem ich hier eine ganze Zeit mitgelesen habe und eigentlich nichts > klares zu erkennen ist, finde ich das alles mittlerweile eher > abschreckend mit C++ auf einem µC zu arbeiten Das liegt eher geringfügig an C++. Das sind typische Designentscheidungen und jeder hat erst mal eine andere Vorstellung im Kopf. Das dauert dann bis man sich auf eine Variante geeinigt hat, weil man sich gegenseitig zu überzeugen versucht. Das man hier in Schriftform kommuniziert (schnarch langsam) tut dann sein Übriges dazu.
TriHexagon schrieb: > Das dauert dann bis man sich auf eine Variante geeinigt hat, weil > man sich gegenseitig zu überzeugen versucht. Guter Punkt: Noch ein Grund, warum ich Euch von 'const', 'private' oder 'final' überzeugen will: Falls man durch seine Programmierung in anderen Modulen versucht, 'const'-Member zu verändern, meckert der Compiler und man merkt es! Sonst merkt man sowas doch gar nicht und blickt gar nicht durch, was da im Hintergrund passiert! Der Optimizer gibt doch kein verlässliches 'warning' aus: "Wenn Sie das hier anders programmiert hätten, hätte ich besser optimieren können." Ein Entfernen eines 'const' oder 'final' oder eine Änderung 'private' -> 'protected' ist dann immer eine bewusste Design-Entscheidung. Falls ich auf dem Holzweg sein sollte: Warum?
:
Bearbeitet durch User
Torsten C. schrieb: > Noch ein Grund, warum ich Euch von 'const', 'private' oder > 'final' überzeugen will: Also, wenn ich was zu sagen hätte, dann hättest du ab sofort 'const'-'private'-'final'-Schreibverbot.
Ralf G. schrieb: > Also, wenn ich was zu sagen hätte, dann hättest du ab sofort > 'const'-'private'-'final'-Schreibverbot. Das ist wenig konstruktiv. Ich versuche mit den anderen hier die (erweiterte) Frage 'C++ auf einem MC, wie geht das am besten?' im Konsens zu beantworten. Torsten C. schrieb: > Falls ich auf dem Holzweg sein sollte: Warum? Darauf hat bisher niemand geantwortet. Was soll ich daraus schließen? Ein Schreibverbot ist gar nicht nötig, das klappt auch ohne. @Ralf G.: Ich versuche hier nicht wie Moby unbeirrbar den Sinn von C++ in Frage zu stellen. Merkst Du was?
:
Bearbeitet durch User
Ralf G. schrieb: > Also, wenn ich was zu sagen hätte, dann hättest du ab sofort > 'const'-'private'-'final'-Schreibverbot. Das ist eigentlich nur die Kurzfassung für: Mach doch mal ein kleines Demoprogramm. Mal mit, mal ohne 'const'-'private'-'final'. Was könnte das Ergebnis sein? Entweder du siehst, dass es nichts bringt, oder alle anderen staunen, was da für ein exquisit optimierter Code entsteht, wenn man richtig Ahnung hat. Bis dahin braucht man das doch nicht immer wiederholen. Oder?
Torsten C. schrieb: > > Ich versuche mit den anderen hier die > (erweiterte) Frage 'C++ auf einem MC, wie geht das am besten?' im > Konsens zu beantworten. > Ich seh das mit meinem momentanen Kenntnisstand so : Kann man MC mit C++ programmieren ? Ja, aber ..... Kann man das auch effektiv ? Ja, aber ... Kann man das auch portabel auf andere MC Familien ? Ja, aber ... Wie geht das am besten ? Kann ich nicht sagen ... Kann man diese letze Frage online ausdiskutieren ? Nein.
Hans-Georg Lehnard schrieb: > Ja, aber ... Das ist doch mal 'ne klare Ansage. Also, wer will, kann hier viele Ansätze finden, einen Port AVR-unabhängig in eine Klasse zu packen, ohne dass es großartig aufträgt (wenn überhaupt!). Das sollte für diesen Teil reichen. Falls jetzt noch jemand Lust hat, könnte man als nächstes darüber diskutieren, inwiefern es sinnvoll ist, den diversen Interruptroutinen eine Klasse zu spendieren (Ich gehe mal ins Rennen: 'Nicht sinnvoll!'). Mit 'Timer' geht's los. (Damit das hier auch schön 'on-topic' bleibt. Peter Dannegger soll daran seine Freude haben!)
Hans-Georg Lehnard schrieb: > Ja, aber ... Schade. Gemeint war das anders: Gut: Der Compiler optimiert über mehrere Module, falls er das kann. Besser: Der Compiler meckert, weil er nicht opimieren kann. Im ersten Fall muss man immer wieder in die *.lss schauen, um sicher zu gehen. Im letzteren Fall muss man nur bei einer Fehlermeldung reagieren und kann bewusst entscheiden, was zu tun ist, ohne wiederholt in die *.lss schauen zu müssen, … … wobei der Compiler genau genommen nicht meckert, 'weil er nicht opimieren kann'^^, sondern, weil man in irgendeinem Modul was gemacht hat, was dazu führt, dass ein Wert zur Laufzeit veränderbar sein muss. Ralf G. schrieb: > Was könnte das Ergebnis sein? Ich dachte, das wäre ohne Demoprogramm offensichtlich, nachdem Hans-Georg Lehnard schrieb: > … das musst du verhindern … Nun verstehe ich das Problem: Es ist gar nicht offensichtlich. Danke für das konstruktive Feedback. Hans-Georg Lehnard schrieb: > Kann ich nicht sagen ... Wie meinst Du das? Das habe ich offenbar bereits zu oft gesagt: => "… doch nicht immer wiederholen. Oder?"^^ Hans-Georg Lehnard schrieb: > Kann man diese letze Frage online ausdiskutieren ? > Nein. Der Wirkungsgrad ist wirklich mies, hast Recht! Ich habe auch manchmal das Gefühl, dass es hier einigen darum geht, zu zeigen, wer hier der schlaueste Programmierer ist, statt konstruktiv an einem Konsens zu arbeiten.
:
Bearbeitet durch User
Ralf G. schrieb: > Falls jetzt noch jemand Lust hat … Was mich betrifft: Ich bin mir wegen des Wirkungsgrades^^ noch unsicher. Ralf G. schrieb: > Mit 'Timer' geht's los. Beim AVR stehen die ISR-Vektoren im Flash-ROM. Da wäre meine erste Fragestellung: Wie programmiert man dann am besten? Bei einem Zustandsautomaten wäre es effizient, wenn man mit einem 'member function pointer' arbeitet:
1 | ISR(…_vect) { |
2 | // vectors are located in flash rom
|
3 | (….*…_ISR)(); |
4 | }
|
Normalerweise ist es jedoch effizienter, wenn die Funktion in der ISR zur Laufzeit nicht veränderbar sein muss. Aber für die Frage von Peter Dannegger benöttigt man ja einen Zustandsautomaten. Den hatten wir ja schon mal andiskutiert. Ich bin gedanklich daher ziemlich bei dem, was Ralf G. schrieb: > Ich gehe mal ins Rennen: 'Nicht sinnvoll!'
:
Bearbeitet durch User
So wird das nichts. Einfach so ins Blaue hinein, kommt nix bei raus. Es müssen konkrete Rahmenbedingungen definiert werden. Soll eine Abstraktion nur für die AVRs gelten? Soll sie nur für 8-Bitter mit ein paar 100 Bytes RAM gelten? Oder ist das Ziel eine Plattform mit 32 Bit und RAM in zweistelligen Kilobyte-Bereich oder noch mehr? Möchte man ein Plattform-Übergreifende Abstraktion mit der man möglichst universell auf "alle" Mikrocontroller zugreifen kann? Usw. Ohne Vorgaben wird das nichts. Die müssen erst geklärt sein.
Ralf G. schrieb: > .... könnte man als nächstes darüber diskutieren, inwiefern > es sinnvoll ist, den diversen Interruptroutinen eine Klasse zu > spendieren (Ich gehe mal ins Rennen: 'Nicht sinnvoll!'). Mit 'Timer' > geht's los. (Damit das hier auch schön 'on-topic' bleibt. Peter > Dannegger soll daran seine Freude haben!) Hallo Ralf, Timer sind wieder so ein wunderbares Beispiel für Ja, aber ... ;) Ja, du hast Recht es macht keinen Sinn irgendetwas in Klassen zu verpacken nur damit es Klassen sind. ISR lassen sich auch nicht direkt verpacken. Dazu gibt es hier im Forum einen schönen Artikel: http://www.mikrocontroller.net/articles/AVR_Interrupt_Routinen_mit_C%2B%2B Aber, ich halte eine Klasse SystemTimer, die einen Hardware Timer kapselt, für sinnvoll weil Timer in einem MC System üblicherweise bergrenzt sind und ich kann nicht z.B. jedem Pin zur Entprellung einen eigenen Timer spendieren. @Torsten Schau dir einfach mal Pedas Entprellroutine in C an. Was spricht dagegen die 1:1 in eine (System)Timer Callback Methode deiner InputPin Klasse zu übernehmen die alle 20ms aufgerufen wird. Da gilt genau das gleiche: Ja das kann man als "richtige" Statemachine verpacken - Aber man muss das nicht ... Wenn ich jetzt einen Eingang nicht einzel betrachte dann macht es vielleicht Sinn eine Entprell Klasse zu haben die gleichzeitig mehrere Pins verwaltet, darin wäre dann vieleicht auch eine "richtige" Statemachine die bessere Lösung. Wenn die Eingänge an einer externen Schieberegisterkette hängen wird man das auf alle Fälle in einer Klasse kapseln. Du hast immer die Entscheidung Aufwand gegen Nutzen.
Hans-Georg Lehnard schrieb: > Schau dir einfach mal Pedas Entprellroutine in C an. Was spricht dagegen > die 1:1 in eine (System)Timer Callback Methode deiner InputPin Klasse zu > übernehmen die alle 20ms aufgerufen wird. Genau! Deshalb habe ich die von mir (mehr oder weniger sinnvollen) vorgeschlagenen Pin-Klassen-Implementierungen immer für den ganzen Port (bzw. für 1..8 Pins) gemacht.
:
Bearbeitet durch User
Theoretiker schrieb: > So wird das nichts. Einfach so ins Blaue hinein, kommt nix bei raus. > > Es müssen konkrete Rahmenbedingungen definiert werden. Soll eine > Abstraktion nur für die AVRs gelten? Soll sie nur für 8-Bitter mit ein > paar 100 Bytes RAM gelten? Oder ist das Ziel eine Plattform mit 32 Bit > und RAM in zweistelligen Kilobyte-Bereich oder noch mehr? Möchte man ein > Plattform-Übergreifende Abstraktion mit der man möglichst universell auf > "alle" Mikrocontroller zugreifen kann? Usw. > > Ohne Vorgaben wird das nichts. Die müssen erst geklärt sein. Die Vorgaben sind wie immer ... Wir müssen schneller auf den Markt, es darf nichts kosten und es muss in 2 Wochen fertig sein. ;)
Hans-Georg Lehnard schrieb: > Was spricht dagegen > die 1:1 in eine (System)Timer Callback Methode deiner InputPin Klasse zu > übernehmen die alle 20ms aufgerufen wird. Da fragst Du den Richtigen: Den, der immer 'Ja aber ...' sagt. ;-) Ich bin inzwischen auch fest davon überzeugt, dass eine uCpp ein Fass ohne Boden ist, selbst falls man sich z.B. nur auf 8-Bit AVR beschränken würde. Der eine braucht 4 GPIOs 'am Stück' um ein Display anzusteuern, der nächste einzelne GPIOs - irgendwo verteilt - als Slave-Select für SPI, der übernächste … usw. Beim Timer mit 2 Kanälen ist das noch schlimmer: Der eine Kanal macht PWM, der andere IC (Input-Compare), oder beide machen PWM oder beide machen IC. Im STM32 sind's noch mehr Kanäle. Wenn man alle Varianten annäherd so effizient umsetzen will, wie mit ASM, dann gibt es eine Inflation von Klassen oder Templates die niemand debuggen oder nach einem Jahr noch verstehen kann. Vorschlag für einen Ausweg (um hier zerrissen zu werden): Man baut sich für das eine oder andere wiederkehrende Problem ein Code-Snippet (für den Manager, siehe Bild), so wie man es gerade braucht. Um nach diesen Überlegungen Deine Frage zu beantworten: Es gibt Projekte, bei denen die Rahmenbedingungen keinen 'Systick' (ich hätte beinahe 'Sysfick' geschrieben) zulassen. Nichts spricht aus meiner Sicht ansonsten dagegen. Falls statt einer uCpp-Lib Interesse an einer Snippet-Sammlung besteht, könnte man eine solche als Projekt starten.
:
Bearbeitet durch User
Als einer der hier interessiert mitliest, habe ich eigentlich nur auf die Erkenntnis von Torsten gewartet. C++ ist sinnvoll für gewisse reine Software-Themen (PT1- Glieder oder allgemein digitale Filter als gutes Beispiel) aber nicht für die harware-nahen Sachen. Torsten hat eh schon ein paar Beispiele genannt; immer dann wenn die Ausnutzung der Hardware "kreativ" wird, ists Essig mit der schönen schnöden Abstraktion in Klassen. Anfangen könnte man schon bei ganz simplen Dingen wie Wired-And auf den Pins: Hier wird der Pin nicht über das PORT-Register gesteuert, sondern über DDR. Das könnte man ja noch hinkriegen... Timer sind aus meiner Sicht ganz schwierig: Dynamisches Umschalten zwischen den Modi, dynamisches Weiterschalten des Output-Compare, ... Software-Interrupts? EEPROM-Write zweckentfremdet? Kreative Verwendung von "artfremden" Dingern als Timer? (z.B. ADC freerunning als Timer) Genau diese kreativen (und genialen) Verwendungen der vielfältigen Möglichkeiten der Hardware kriegst du nie in ein Objekt-Modell. Willst du das alles C++-mäßig simplifizieren, bist du bei Arduino. Nett, aber beschränkt. Sobald du mehr willst, wirfst du das über Bord. Schlimmstenfalls hast du dann ein mischmasch, das keiner mehr lesen und verstehen kann. Peters Frage dürfte damit beantwortet sein.
Ralf G. schrieb: > Vor allen Dingen: Das ist nicht das, was ich mir unter effizient > vorgestellt habe. Vielleicht hat ein Profi 'ne bessere Idee. O je, ihr habt diesen Unsinn ja noch immer nicht zu Grabe getragen. Ja, ich hab ne bessere Idee - und ich hatte sie bereits viel weiter oben geäußert: Laßt all diesen C++ Krimskram bleiben und kommt auf den Teppich herunter. Vernünftige Peripherie-Treiber, die eine echte Arbeit leisten und ne hardwareunabhängige Schnittstelle zur Applikation herstellen, sind viel effektiver, viel verständlicher und machen die Applikation viel eher portabel, sofern das überhaupt geht. Sowas wie "LED1.toggle = KEY1.press" oder auch "LED1::setOutput(xpcc::Gpio::Low);" sind Mumpitz und zu nichts nütze. Das ist nur digitale Onanie. W.S.
Und ich habe darauf gewartet das jetzt wieder einer behauptet C++ sei dafür grundsätzlich nicht geeignet. Das jemand die Komplexität eines Problemes unterschätzt ist doch völlig normal, das passiert jedem und vor allen Dingen wenn er neu auf dem (embedded) Gebiet ist. Das ist aber überall so völlig unabhängig von der Sprache. Und wie entwerfe ich meine Klassen gehört nicht zu den leichtesten Übungen bei objektorientierten Sprachen. Es war doch nicht Tomas, sondern die lieben Assembler Programmierer, die geschrieen haben das wär doch alles trivial. Gegen Vorurteile kämpfen ist wie gegen Windmühlen. Und bei den SOC wie zum Beispiel dem Zynq, das weisst du ja als Softwerker nicht mal was dein Kollege dir für eine Hardware ins FPGA bastelt. Das wird es erst richtig Hardwarenah und da möchte ich mal sehen wer dann noch in Assembler programmieren will.
:
Bearbeitet durch User
Hallo Michael, Michael Reinelt schrieb: > Meines Wissens nach stimmt das nicht, GCC kann seit ca. 5 jahren LTO > (Link Time Optimization) und die macht genau das. Wenn das korrekt funktionieren würde, dann würde die Kompilatgröße nicht explodieren, sobald man den Quellcode auf mehrere Übersetzungseinheiten aufteilt -- aber sie tut es. Das zeigt mir, daß die LTO in diesem Falle offenbar nicht wie gewünscht funktioniert, ein oberflächlicher Blick ins Mapfile scheint meinen Verdacht zu bestätigen. Schade. Liebe Grüße, Karl
Hallo F., F. Fo schrieb: > Karl Käfer schrieb: >> Dahinter verbirgt sich eine Art C++, > > Arduino language is based on C/C++. It links against AVR Libc and allows > the use of any of its functions; see its user manual for details. > > Wer es selber nachlesen will: > http://arduino.cc/en/pmwiki.php?n=Reference/HomePage Der Sinn erschließt sich, wenn Du den ganzen Satz liest. Der geht nämlich weiter mit: "die dafür gemacht und optimiert wurde, von absoluten Anfängern und Nichttechnikern benutzt zu werden". Die "eine Art" bezieht sich also auf den Teil "die dafür gemacht und optimiert wurde". Liebe Grüße, Karl
Mein lieber Karl, mein Englisch ist zwar nicht (mehr) auf C Niveau, aber du liest da was (oder an anderer Stelle?) was da nicht steht. Es ist wohl schon 30 Jahre her, dass ich mich dort mal längere Zeit aufgehalten hatte, aber aus dem Satz, Karl Käfer schrieb: > Arduino language is based on C/C++., kann man nicht mehr raus lesen, als da steht. Da steht, dass Arduino auf C/C++ basiert. Nicht mehr und nicht weniger. Ob dir das nun gefällt oder nicht, so steht es da. Ich hatte ja mit Arduino angefangen, aber ich habe mir natürlich nicht die "Basis" dazu angesehen, noch fehlt mir bis heute das Wissen das gut genug zu beurteilen. Ich will ja nicht behaupten, dass du unrecht hast, aber dass das da steht wie es steht, ist für jeden zu lesen.
:
Bearbeitet durch User
Hi Ralf, Ralf G. schrieb: > Karls Beispiel > Beitrag "Re: C++ auf einem MC, wie geht das?" in Module > verteilt, dem gcc mit LTO auf die Sprünge geholfen... und? ... > funktioniert!! Das muss aber nicht immer so sein. Oh, das funktioniert doch? Ui. Dann nehme ich alles zurück und behaupte das Gegenteil! Liebe Grüße, Karl
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Kann man diese letze Frage online ausdiskutieren ? > > Nein. Daß wir in dieser Diskussion immer wieder festhängen, liegt primär an ihrem rekursiven Charakter. Das betrifft vor allem Nebensächliches wie "Templates oder nicht", aber auch "const"-, "private"- und "final"-Deklarationen. Ohne konkreten Code als Diskussionsgrundlage laufen diese Fragen aber leider immer wieder ins Leere. Leider scheine ich hier aber derzeit der Einzige zu sein, der aktiv an Code arbeitet und diesen wiederholt gepostet hat. Aus mir unerfindlichen Gründen wird über meinen Code aber kaum diskutiert. Stattdessen verliert sich die Diskussion wiederholt in Details, hängt aber mangels konkreten Codes als Diskussionsgrundlage frei in der Luft. Mir ist unklar, wie wir das lösen können. Aber vielleicht mag ja der Eine oder andere einfach mal meinen Code lesen und eine fundierte Kritik daran äußern. Vielen Dank. Liebe Grüße, Karl
Hallo Michael, Michael Reinelt schrieb: > C++ ist sinnvoll für gewisse reine Software-Themen (PT1- Glieder oder > allgemein digitale Filter als gutes Beispiel) aber nicht für die > harware-nahen Sachen. Hast Du mal einen Blick in den von mir geposteten Code geworfen? Wenn ja, wüßte ich gerne, wie Du zu dieser Aussage kommst. Liebe Grüße, Karl
Hallo W., W.S. schrieb: > O je, ihr habt diesen Unsinn ja noch immer nicht zu Grabe getragen. > > Ja, ich hab ne bessere Idee - und ich hatte sie bereits viel weiter oben > geäußert: Laßt all diesen C++ Krimskram bleiben und kommt auf den > Teppich herunter. Vernünftige Peripherie-Treiber, die eine echte Arbeit > leisten und ne hardwareunabhängige Schnittstelle zur Applikation > herstellen, sind viel effektiver, viel verständlicher und machen die > Applikation viel eher portabel, sofern das überhaupt geht. Sowas wie > "LED1.toggle = KEY1.press" oder auch "LED1::setOutput(xpcc::Gpio::Low);" > sind Mumpitz und zu nichts nütze. Das ist nur digitale Onanie. Ich bilde mir ein, mittlerweile ziemlich eindeutig bewiesen zu haben, daß man mit C++ auch für Mikrocontroller elegante Programme schreiben kann, die kein einziges Byte größer sind als ihr Äquivalent in C, dafür aber sehr viel besser zu Lesen und zu Warten sind. Weißt Du, labern und mosern kann jeder. Deswegen schlage ich vor, daß wir uns die Sache hier ganz einfach machen. Wenn Du es schaffst, mit C oder Assembler etwas zu implementieren, das a) genau so lesbar ist wie meine angehängte "main.cpp" und b) zu nicht mehr als 146 Byte Code kompiliert, dann laß uns einfach mal Deinen großartigen Code sehen. Wenn Du das nicht schaffst, dann schlage ich vor, daß Du Dich irgendwoanders konstruktiver einbringst und uns hier nicht weiter störst. Jetzt bin ich aber wirklich mal gespannt, ob Du Manns genug bist, Dich dieser einfachen Herausforderung zu stellen. Viel Erfolg! Liebe Grüße, Karl
Karl Käfer schrieb: > Hast Du mal einen Blick in den von mir geposteten Code geworfen? Ja, habe ich. > Wenn ja, wüßte ich gerne, wie Du zu dieser Aussage kommst. Das bissi LED-einschalten ist aber noch lange nicht "kreative" Verwendung von Hardware. mach mal Wired-And dazu, Entprellung, einen Timer mit OCR-Leiter, dann schauen wir weiter.
Karl Käfer schrieb: > Leider scheine ich hier aber derzeit der > Einzige zu sein, der aktiv an Code arbeitet und diesen wiederholt > gepostet hat Nicht wirklich der einzige! Oder meinst Du das ernst? Ich habe im Moment den USART und einen Timer an der Backe und kann auch gern Beispiele posten. Dadurch wird der Thread jedoch nicht übersichtlicher. Also bleiben wir beim GPIO: Karl Käfer schrieb: > Aus mir unerfindlichen Gründen wird über meinen Code aber kaum > diskutiert. Schau doch bitte mal, ob bei Deiner 'class Pin' im *.lss wirklich immer 'sbi' und 'cbi' auftauchen. Ich habe da Bedenken. Falls immer 'sbi' und 'cbi' compiliert wird, liegt der Mangel an Kritik vielleicht daran, dass keiner was dran auszusetzen hat, das Kapitel also abgeschlossen ist.
:
Bearbeitet durch User
Hallo Michael, Michael Reinelt schrieb: > Torsten hat eh schon ein paar Beispiele genannt; immer dann wenn die > Ausnutzung der Hardware "kreativ" wird, ists Essig mit der schönen > schnöden Abstraktion in Klassen. > [...] > Timer sind aus meiner Sicht ganz schwierig: Dynamisches Umschalten > zwischen den Modi, dynamisches Weiterschalten des Output-Compare, ... > [...] > Genau diese kreativen (und genialen) Verwendungen der vielfältigen > Möglichkeiten der Hardware kriegst du nie in ein Objekt-Modell. Man muß nicht alles in Klassen und Objektmodelle stopfen. Und mit einer C++-Bibliothek, die auf der AVR-Libc aufbaut, steht deren Funktionalität auch weiterhin uneingeschränkt zur Verfügung. > Willst du das alles C++-mäßig simplifizieren Entschuldige, darum geht es doch gar nicht. Es reicht doch schon aus, die vielen "normalen", "unkreativen" Dinge les- und wartbarer zu machen. Wenn Du Deine Hardware voll ausreizen und kreativ nutzen willst, kein Problem: dann kannst Du C und sogar Assembler innerhalb von C++-Code nutzen. Aber wenn Du nur "normale" Sachen machen, und zum Beispiel einen Pin wackeln willst, was sicherlich 80 oder sogar 90% der Anwendungsfälle abdeckt: dann hilft C++, den Code besser zu organisieren. > Schlimmstenfalls hast du dann ein mischmasch, das keiner mehr lesen und > verstehen kann. Was meinst Du? C ist (weitestgehend) eine Untermenge von C++. Wer C++ benutzt, muß zwangsläufig C können. Könntest Du Deinen Einwand anhand eines Codebeispiels aufzeigen? Liebe Grüße, Karl
Karl Käfer schrieb: > einen Pin > wackeln willst, was sicherlich 80 oder sogar 90% der Anwendungsfälle > abdeckt Also bei mir ist das signifikant kleiner 10% Karl Käfer schrieb: >> Schlimmstenfalls hast du dann ein mischmasch, das keiner mehr lesen und >> verstehen kann. > > Was meinst Du? C ist (weitestgehend) eine Untermenge von C++. Wer C++ > benutzt, muß zwangsläufig C können. Könntest Du Deinen Einwand anhand > eines Codebeispiels aufzeigen? Nehmen wir mal an ich hätte eine Wunderwuzzi-Timer-Klasse. Dann hab ich einen "kreativen" Anwendungsfall, den die Klasse überhaupt nicht abdeckt, und ich muss erst wieder an TCCR "rummachen". Das meine ich mit MischMasch.
Hallo, Karl Käfer schrieb: > Wenn das korrekt funktionieren würde, dann würde die Kompilatgröße nicht > explodieren, sobald man den Quellcode auf mehrere Übersetzungseinheiten > aufteilt -- aber sie tut es. Da Ralf festgestellt hat, daß das nicht der Fall ist, und ich dies anhand meines Codes verifizieren konnte, ist diese Aussage widerlegt. Liebe Grüße, Karl
Michael Reinelt schrieb: > Nehmen wir mal an ich hätte eine Wunderwuzzi-Timer-Klasse. Dann hab ich > einen "kreativen" Anwendungsfall, den die Klasse überhaupt nicht > abdeckt, und ich muss erst wieder an TCCR "rummachen". Das meine ich mit > MischMasch. Wieso. TCCR wird als protected definiert und die Klasse WunderwuzziWurschtel wird von Wunderwuzzi abgeleitet. Wers braucht, kann es doch machen.
Icn bin gerade am Timer mit zwei Kanälen und es geht mir wie dem Beamten:
1 | Ein Beamter macht Urlaub auf einem Bauernhof. Der Bauer gibt ihm |
2 | was zu tun: „Hier sind zwei Schüsseln, eine große und eine kleine |
3 | und dort hinten ist ein Sack Kartoffeln. Sortieren Sie bitte die |
4 | großen Kartoffeln in die große Schüssel und die kleinen Kartoffeln |
5 | in die kleine!“ Nach einer Stunde kommt der Bauer wieder. Da sitzt |
6 | der Beamte vor seinen zwei Schüsseln. In der kleinen ist eine |
7 | kleine Kartoffel, in der großen ist eine große Kartoffel und in |
8 | der Hand hält er eine mittelgroße und fragt den Bauern: „Ist die |
9 | jetzt groß oder klein?“ |
Die Methoden für Kanal A und für Kanal B müssen unterschiedlich sein. Mache ich nun zwei Methoden mit unterschiedlichen Bezeichnungen oder eine gemeinsame mit einem Parameter und if / else? Man kann sich mit diesem c++-Kram ganz schön aufhalten. 'if/else' ist marginal ineffizienter aber schneller global geändert. Bei unterschiedlichen Bezeichnungen müsste man mit 'suchen/ersetzen' arbeiten oder doch wieder mit #define arbeiten. Ich überlege schon länger als der Beamte. :-(
Torsten C. schrieb: > Die Methoden für Kanal A und für Kanal B müssen unterschiedlich sein. Wiso? Was ist beim einen anders als beim anderen? Kann man es nicht per Template parameter lösen?
Nur mal so nebenbei: ZX,Y und Z sind Pointer-Register. Wenn man sie nicht als solche benutzt, sondern z.B. XL als 8-Bit Wert, wie hier oft zu sehen, dann sollte man es R26 nennen. Andernfalls setzt man sich dem Vorwurf aus, die Programmfunktion verschleiern zu wollen. Du hast da ein tolles Beispiel geliefert für das, was viele an ASM-Programmen stört. Ein Programm, nach meinem Verständnis, soll im Source-Code die Problemlösung beschreiben. Das kann man mit den schon zu sehenden Beispielen sehr gut. Wenn ich in C++
1 | Led7.toggle(); |
schreibe, dann kann ich in der entsprechenden Klasse die HW-Detail, alter/neuer AVR, mit PIN-Toggle HW oder ohne versteckt. Einmal getestet, ist das gegessen. Und ob der Compiler dann 0 oder n temp. Register braucht um das umzusetzen, darüber haben sich andere schon den Kopf zerbrochen. Wenn es also wirklich Bedarf nach ASM gibt (wegen Timing, etc) dann muß da für mich deutlich mehr Kommentar als Code stehen. Gratulation, Du hast meinen Vorurteil bestätigt. BTW, ich programmieren kleine bis ganz große Kisten nun seit 35Jahren (und lebe gut davon), da war viel Zeit aus Fehlern zu lernen.
chris_ schrieb: >>Led7.toggle(); > > Hmm ... das hier vielleicht: > > http://playground.arduino.cc/Code/DigitalToggle Ein schönes Beispiel wie man es nicht machen sollte ... void digitalToggle(uint8_t P) { *portInputRegister(digitalPinToPort(P)) = digitalPinToBitMask(P); } Ich gehe jetzt mal davon aus, das wir einen Ausgang Toggeln wollen und das der Code irgendwie funktioniert ... portInputRegister(digitalPinToPort(P)) // das liefert uns die Adresse eines Ausganges ? *portInputRegister(..) = digitalPinToBitMask(P) // das toggelt ? void digitalToggle(uint8_t P) // das funktioniert auf einem ARM ?
Hans-Georg Lehnard schrieb: > void digitalToggle(uint8_t P) > { > *portInputRegister(digitalPinToPort(P)) = digitalPinToBitMask(P); > } Hmm. Könnte ich mir jetzt was darunter vorstellen. Schlimm finde ich das nicht. Außer: PINx beschreiben zum Umschalten, ist jetzt eben 'die allerneueste Mode' ;-) Funktioniert aber nicht immer. (Ich lass das erstmal.)
Ralf G. schrieb: > Hans-Georg Lehnard schrieb: >> void digitalToggle(uint8_t P) >> { >> *portInputRegister(digitalPinToPort(P)) = digitalPinToBitMask(P); >> } > > Hmm. Könnte ich mir jetzt was darunter vorstellen. Schlimm finde ich das > nicht. > Außer: > PINx beschreiben zum Umschalten, ist jetzt eben 'die allerneueste Mode' > ;-) > Funktioniert aber nicht immer. (Ich lass das erstmal.) Für dich ist das völlig normal, das man eine Funktion, die einen Apfel zurückliefern soll, "GibMirEineBirne" tauft und das das alles so oder so nur Modeerscheinungen sind ? Für mich ist der Thread damit auch erledigt ...
Hans-Georg Lehnard schrieb: > Ralf G. schrieb: >> Hans-Georg Lehnard schrieb: >>> void digitalToggle(uint8_t P) >>> { >>> *portInputRegister(digitalPinToPort(P)) = digitalPinToBitMask(P); >>> } >> >> Hmm. Könnte ich mir jetzt was darunter vorstellen. Schlimm finde ich das >> nicht. >> Außer: >> PINx beschreiben zum Umschalten, ist jetzt eben 'die allerneueste Mode' >> ;-) >> Funktioniert aber nicht immer. (Ich lass das erstmal.) > > Für dich ist das völlig normal, das man eine Funktion, die einen Apfel > zurückliefern soll, "GibMirEineBirne" tauft und das das alles so oder so > nur Modeerscheinungen sind ? Hier wird doch nichts geliefert!? Hier wird nur was gemacht. '_void_ digitalToggle(uint8_t P)' heißt für mich: 'Digitales Umschalten von...' Wenn jetzt in 'P' Port und Pin codiert ist (Hi/Low im Byte) dann holt sich 'digitalPinToPort(P)' die Portadresse, 'digitalPinToBitMask(P)' die Pinmaske, ... '*portInputRegister' ..., hmm, hätte ich jetzt mit so einer Konstruktion verglichen: '_SFR_MEM8(sfr_regs::_pin)' Und zum Schluss wird sowas wie 'PORTB = 1 << 6' draus. > Ralf G. schrieb: > PINx beschreiben zum Umschalten, ist jetzt eben 'die allerneueste Mode' > ;-) > Funktioniert aber nicht immer. (Ich lass das erstmal.) Das würde ich dann noch mal hervorheben wollen: ->> Funktioniert aber nicht immer. <<-
Gerade weil das "nicht immer funktioniert", ist es gut eine Template Klasse zu haben, die weiß, wann es geht und die der Compiler zu den 1..3 AVR-Opcodes reduziert, die jeweils gebraucht werden.
Ralf G. schrieb: > Hier wird doch nichts geliefert!? > Hier wird nur was gemacht. > '_void_ digitalToggle(uint8_t P)' heißt für mich: 'Digitales Umschalten > von...' > Wenn jetzt in 'P' Port und Pin codiert ist (Hi/Low im Byte) dann holt > sich > 'digitalPinToPort(P)' die Portadresse, 'digitalPinToBitMask(P)' Kann man sich ja noch vorstellen die > Pinmaske, ... '*portInputRegister' ..., hmm, hätte ich jetzt mit so > einer Konstruktion verglichen: '_SFR_MEM8(sfr_regs::_pin)' > *portInputRegister(..) ist eine Funktion und die hat einen Pointer als Rückgabe der dereferenziert wird ... Mir ist schon klar was da gemacht wird aber warum packe ich da das wort input in den Funktionsnamen. > Und zum Schluss wird sowas wie 'PORTB = 1 << 6' draus. > >> Funktioniert aber nicht immer. (Ich lass das erstmal.) > Das würde ich dann noch mal hervorheben wollen: > ->> Funktioniert aber nicht immer. <<- PORTB = 1 << 6' toggelt niemals einen Pin auch nicht manchmal Das setzt bit6 auf 1 und alle anderen Bits auf 0.
Hans-Georg Lehnard schrieb: > *portInputRegister(..) ist eine Funktion und die hat einen Pointer als > Rückgabe der dereferenziert wird ... Mir ist schon klar was da gemacht > wird aber warum packe ich da das wort input in den Funktionsnamen. Weil das Ding die Adresse von PINx liefert, nicht von PORTx.
Hans-Georg Lehnard schrieb: > PORTB = 1 << 6' toggelt niemals einen Pin auch nicht manchmal Hat ja auch keiner behauptet. Aber PINB = 1 << 6 toggelt auf neueren AVRs einen Pin.
:
Bearbeitet durch Moderator
Jörg Wunsch schrieb: > Hans-Georg Lehnard schrieb: >> PORTB = 1 << 6' toggelt niemals einen Pin auch nicht manchmal > > Hat ja auch keiner behauptet. > > Aber PINB = 1 << 6 toggelt auf neueren AVRs einen Pin. Ok, danke das ist mir neu und bisher noch bei keinen anderen MC begegnet. Und ich dachte auch immer Arduino kennt nicht nur neue Avrs ...
1 | #include <avr/io.h> |
2 | |
3 | int main(void) |
4 | { |
5 | DDRA |= (1<<5); |
6 | fa: 0d 9a sbi 0x01, 5 ; 1 |
7 | PORTA ^= (1<<5); |
8 | fc: 92 b1 in r25, 0x02 ; 2 |
9 | fe: 80 e2 ldi r24, 0x20 ; 32 |
10 | 100: 89 27 eor r24, r25 |
11 | 102: 82 b9 out 0x02, r24 ; 2 |
12 | |
13 | DDRB |= (1<<6); |
14 | 104: 26 9a sbi 0x04, 6 ; 4 |
15 | PINB = (1<<6); |
16 | 106: 80 e4 ldi r24, 0x40 ; 64 |
17 | 108: 83 b9 out 0x03, r24 ; 3 |
18 | 10a: ff cf rjmp .-2 ; 0x10a <main+0x10> |
Hmm ich spare bei dem Toggle Befehl mit dem Input Pin Register 2Byte im Flash und 2 Takte während der Laufzeit dafür handle ich mir etwas nicht portables in einer "portablen" Lib ein. Kann man das wenigstens beim AVR mit #if _AVR_ARCH_ >= XXX abfangen ?
:
Bearbeitet durch Moderator
Hans-Georg Lehnard schrieb: > Kann man das wenigstens beim AVR mit > #if AVR_ARCH >= XXX abfangen ? Kaum, denn es kann immer wieder mal was Neues dazukommen. Übrigens ... bei den ATxmega-Controllern sieht's nochmal anders aus. http://www.atmel.com/Images/doc8077.pdf http://www.atmel.com/Images/Atmel-8331-8-and-16-bit-AVR-Microcontroller-XMEGA-AU_Manual.pdf
Moby AVR schrieb im Beitrag #3995376: > Um die geht es aber hier nicht, sondern um einfache Mikrocontroller. Als wir angefangen haben, waren die Computer in der 10000 DM Klasse so groß wie heutigen Mikrocontroller. Jede Sprache hat ihre Berechtigung. Nur hier geht es nicht um AS sondern um C++.
Konrad S. schrieb: > Weil das Ding die Adresse von PINx liefert, nicht von PORTx. Ja, verdammt. :-( ich meinte ja auch PINx (kleiner Schreibfehler...) Ralf G. schrieb: > PINx beschreiben zum Umschalten, ist jetzt eben 'die allerneueste Mode' > ;-)
Hans-Georg Lehnard schrieb: > Hmm ich spare bei dem Toggle Befehl mit dem Input Pin Register 2Byte im > Flash und 2 Takte während der Laufzeit dafür handle ich mir etwas nicht > portables in einer "portablen" Lib ein. > > Kann man das wenigstens beim AVR mit > #if AVR_ARCH >= XXX abfangen ? Also ich ignoriere das erstmal :-)
Ralf G. schrieb: > Ja, verdammt. :-( ich meinte ja auch PINx (kleiner Schreibfehler...) Und auch das solltest du zurücknehmen .. > Und zum Schluss wird sowas wie 'PORTB = 1 << 6' draus. wenn du 'PINB = 1 << 6' meinst ... Tu das nie wieder ich könnte es dir übel nehmen ;)
Hallo Jörg, Jörg Wunsch schrieb: > Hans-Georg Lehnard schrieb: >> PORTB = 1 << 6' toggelt niemals einen Pin auch nicht manchmal > > Hat ja auch keiner behauptet. > > Aber PINB = 1 << 6 toggelt auf neueren AVRs einen Pin. Was genau heißt in diesem Zusammenhang "neuere AVRs"? Liebe Grüße, Karl
Jörg Wunsch schrieb: > Aber PINB = 1 << 6 toggelt auf neueren AVRs einen Pin. Das konnten schon ja schon die uralten 2313er Tinys...
Hallo Karl, wenn du noch dabei bist eine Lib zu bauen würde ich solche "Spezialitäten" ehrlich gesagt nicht berücksichtigen und den portablen Weg wählen. Sonst hast du wirklich ein Fass ohne Boden. Ähnliches gilt auch für den Pull up der hat mal ein eigenes Register (PUE) dann wird er wieder über das PORTx Register eingeschaltet. Das Problem dabei ist du musst nach jeweiligen Chip unterscheiden und auch innerhalb eines Chips funtioniert das nur wenn der PIN als digitaler Ausgang benutzt wird. Wenn der Pin z.B. als Timer Ausgang konfiguriert ist funktioniert das nicht mehr. Falls ich die Datenblätter falsch verstanden habe bitte korrigieren. Die Logik von Atmel ist: Ein "ReadOnlyRegister" ist ein Register, wenn man da was reinschreibt ändert sich zwar nichts in diesem Register aber in einem anderen kann das manchmal der Fall sein. Wenn du eine Anwendung wie den Transistortester schreibts, der bei der Kapazitätsmessung Ladungsimpulse definierter Länge auf den Kondensator gibt, kann das Vorteile bringen weil es die Auflösung erhöht, aber für eine LED blinken zu lassen ist das völlig egal ob die 2 Takte länger braucht oder nicht. Und LED blinken lassen werden die Hauptanwender deiner Lib sein.
:
Bearbeitet durch User
Hans-Georg Lehnard schrieb: > Das Problem dabei ist du musst nach jeweiligen Chip unterscheiden und > auch innerhalb eines Chips funtioniert das nur wenn der PIN als > digitaler Ausgang benutzt wird. Wenn der Pin z.B. als Timer Ausgang > konfiguriert ist funktioniert das nicht mehr. Man kann ja eine toogle Methode zur verfügung stellen mit einer weitgehend allgemeinen Lösung. Und dann die Speziallösungen mit dem Präprozessor zugänglich machen. Das ist ja das schöne an einer Abstraktionsschicht im Vergleich mit dem direkten Registerhandling.
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Hmm ich spare bei dem Toggle Befehl mit dem Input Pin Register 2Byte im > Flash und 2 Takte während der Laufzeit dafür handle ich mir etwas nicht > portables in einer "portablen" Lib ein. Was ist eine Abstraktionsschicht? Liebe Grüße, Karl
Hallo Konrad, Konrad S. schrieb: > Hans-Georg Lehnard schrieb: >> Kann man das wenigstens beim AVR mit >> #if AVR_ARCH >= XXX abfangen ? > > Kaum, denn es kann immer wieder mal was Neues dazukommen. Bitte korrigiere mich, wenn ich etwas Falsches sage, aber wenn mich meine Erinnerung nicht trügt, macht man etwas mit solchen Bibliotheken, das man "Pflege" nennt. ;-) > Übrigens ... bei den ATxmega-Controllern sieht's nochmal anders aus. Ja... äh, und? Liebe Grüße, Karl
@Karl Käfer Mit der Abfrage von AVR_ARCH erfährt man etwas über den Befehlssatz, nicht über Peripherie-Eigenschaften. Abfragen (speziell Vergleichsoperatoren wie >= usw.) lassen sich zuverlässig nur für diejenigen Eigenschaften auf ein Makro anwenden, für die das Makro designt wurde. Mag sein, dass einige Peripherie-Eigenschaften mit AVR_ARCH korrespondieren, aber ich halte das nicht für eine zugesicherte Eigenschaft. Die Bibliotheks-Pflege müsste für jeden neu hinzukommenden Controller untersuchen, ob die bisherigen Annahmen bezüglich AVR_ARCH und Peripherie-Eigenschaften noch zutreffen. Apropos ... noch sehe ich so eine Bibliothek in weiter Ferne - wenn dieses verschwommene Pünktchen am Horizont überhaupt eine Bibliothek ist, kein Fatamorgähnchen. ;-) Karl Käfer schrieb: > Ja... äh, und? Nun, die ATxmega-Ports weisen erhebliche Unterschiede zu den ATtinys/ATmegas auf, sowohl hinsichtlich Konfiguration als auch bezüglich Benutzung. Die Ports lassen sich ausgehend von ihrer jeweiligen Basisadresse über eine Struktur ansprechen, eine zugesicherte Eigenschaft.
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > wenn du noch dabei bist eine Lib zu bauen würde ich solche > "Spezialitäten" ehrlich gesagt nicht berücksichtigen und den portablen > Weg wählen. Sonst hast du wirklich ein Fass ohne Boden. Ähnliches gilt > auch für den Pull up der hat mal ein eigenes Register (PUE) dann wird er > wieder über das PORTx Register eingeschaltet. In meinen Partdescription-XML-Dateien, 194 Stück an der Zahl, gibt es 139 ATtiny und ATmega, welche ich für mich zunächst einmal als Zielplattform festgelegt habe. Von diesen 139 Tinys und Megas haben insgesamt 7 ein PUE[ABC]-Register und insgesamt 29 können einen Pin mittels Beschreibens des PIN-Registers umschalten. Dafür arbeite ich derzeit an einem kleinen Codegenerator, der aus diesem XML dann Präprozessor-Code generiert. > Das Problem dabei ist du musst nach jeweiligen Chip unterscheiden und > auch innerhalb eines Chips funtioniert das nur wenn der PIN als > digitaler Ausgang benutzt wird. Wenn der Pin z.B. als Timer Ausgang > konfiguriert ist funktioniert das nicht mehr. Wenn ich vor jedem "toggle()" testen will, daß der Pin nicht für Timer konfiguriert ist, lande ich wieder bei Arduino. Die machen das IIRC so, und für deren anvisierte Zielgruppe ist es auch absolut sinnvoll. Wie oben schon einmal erwähnt, ist das aber eine philosophische Frage. Nach meiner Auffassung ist es die wichtigste Aufgabe einer Bibliothek, Dinge zu ermöglichen, und nicht, sie zu verhindern. Deswegen will ich mögliche Fehler höchstens dann abfangen, wenn es nichts kostet. Damit möchte ich maximale Effizienz und Flexibilität erreichen. Wer es anders haben möchte, ist nicht meine Zielgruppe und hat mit Arduino eine funktionierende, aber weniger flexible und effiziente Alternative. Liebe Grüße, Karl
Karl Käfer schrieb: > Hallo Hans-Georg, > Was ist eine Abstraktionsschicht? Das ist genau das, was dieser Diskussion fehlt ;) Ich zittiere mal Wikipedia ... Eine Abstraktion bezeichnet meist den induktiven Denkprozess des Weglassens von Einzelheiten und des Überführens auf etwas Allgemeineres oder Einfacheres. Wir verzetteln uns hier immer wieder in Kleinigkeiten und in die Tiefen der Avr Bitpopelei. Dem Hobbyanwender ist diese Diskussion völlig wurscht, der bestellt sich den passenden Chip, programmiert ihn in seine Lieblingssprache, und damit ist das Problem erledigt. Der gewerbliche Anwender, der mindestens 10 Jahre Ersatzteile liefern muss kann das leider nicht so einfach tun ..
Karl Käfer schrieb: > Hallo Hans-Georg, ... > Nach meiner Auffassung ist es die wichtigste Aufgabe einer Bibliothek, > Dinge zu ermöglichen, und nicht, sie zu verhindern. Kann man so sehen ... > Deswegen will ich mögliche Fehler höchstens dann abfangen, wenn es nichts > kostet. Damit möchte ich maximale Effizienz und Flexibilität erreichen. > > Wer es anders haben möchte, ist nicht meine Zielgruppe > Genau so ist es du definierst dein Sytem (Lib) und entscheidest was es kann und was nicht.
Über Geschmack lässt sich nicht streiten. Hans-Georg Lehnard schrieb: > du definierst dein Sytem (Lib) und entscheidest was es > kann und was nicht. … wobei ich Karl Käfers 'Geschmack' teile. Ich bin - wie gesagt - gerade am Timer und komme auch nur viel langsamer voran als Karl (viele Baustellen, pro Tag nur mal 1-2h Zeit, …). Eine 'mikrocontroller.net-Lib' darf man das sicher nicht nennen, wenn es keinen Konsens gibt. Ich versuche möglichst nach der gleichen Philosophie wie Karl weiter zu machen. Wenn ich seine GPIOs nehme, passt's wenigstens. Karl Käfer schrieb: > Dafür arbeite ich > derzeit an einem kleinen Codegenerator, der aus diesem XML dann > Präprozessor-Code generiert. Cool. :-)
Hallo Hans-Georg, Hans-Georg Lehnard schrieb: > Karl Käfer schrieb: >> Hallo Hans-Georg, >> Was ist eine Abstraktionsschicht? > > Das ist genau das, was dieser Diskussion fehlt ;) Was ich sagen wollte, ist: für spezifische Details wie PUE-Register oder die Frage, wie genau die Controller-Hardware einen Pin toggeln kann, hat der liebe Gott (oder jemand anderes) die Abstraktionsschicht erfunden. Wenn der GPIO-Pin die Methoden "toggle()" und "setPullup()" anbietet, ist es für den Benutzer der Bibliothek gleichgültig, wie sie intern implementiert sind. > Wir verzetteln uns hier immer wieder in Kleinigkeiten und in die Tiefen > der Avr Bitpopelei. Gerade diese "Avr Bitpopelei" halte ich aber für extrem wichtig, da sie die Grundlage für etliche andere Dinge ist. Wer diese nicht sauber hinbekommt, wird sich schwer tun, damit einen performanten, effizienten Treiber für zum Beispiel ein Display hinzubekommen. Liebe Grüße, Karl
Hallo Torsten, Torsten C. schrieb: > Über Geschmack lässt sich nicht streiten. Doch, natürlich kann man das. Es bringt nur nichts, weil man nie zu einem Ergebnis kommt. > Hans-Georg Lehnard schrieb: >> du definierst dein Sytem (Lib) und entscheidest was es >> kann und was nicht. > > … wobei ich Karl Käfers 'Geschmack' teile. Ich bin - wie gesagt - gerade > am Timer und komme auch nur viel langsamer voran als Karl (viele > Baustellen, pro Tag nur mal 1-2h Zeit, …). Ehrlich gesagt habe ich nicht die geringste Idee, wie man die Timer der AVRs in C++ abbilden könnte. > Karl Käfer schrieb: >> Dafür arbeite ich >> derzeit an einem kleinen Codegenerator, der aus diesem XML dann >> Präprozessor-Code generiert. > > Cool. :-) Die ersten beiden Generatoren habe ich hier einmal angehängt, aber der Code für die USARTS gefällt mir noch nicht so richtig. Für Vorschläge und Ideen werden bin ich gerne offen. Liebe Grüße, Karl
Hans-Georg Lehnard schrieb: > Hmm ich spare bei dem Toggle Befehl mit dem Input Pin Register 2Byte im > Flash und 2 Takte während der Laufzeit dafür handle ich mir etwas nicht > portables in einer "portablen" Lib ein. Ganz so einfach ist das nicht, denn read-modify-write ist nicht atomar. Hat man eine ISR, die einen anderen Pin an demselben Port ändert, geht das irgendwann schief. Es sei denn, man denkt dran, das ganze mit ATOMICBLOCK zu umschließen. Zum eigentlichen Thema: Ich programmiere seit einiger Zeit meine XMegas objektorientiert. Das hat mehrere Gründe: Erst einmal ist das für mich persönlich die Art, wie ich am besten Programmieraufgaben löse. Das ist rein subjektiv und gilt natürlich nicht allgemein. Bei mehrfach vorhandener, identischer Hardware (z.B. bis zu 8 USARTs beim XMega) stösst die herkömmliche Programmierung mit einer festen Kodierung der Hardware definitiv an ihre Grenzen. Eine dynamische Bindung der Hardware an jeweils eine Instanz einer Klasse zur Laufzeit ist da deutlich einfacher und flexibler. Ich programmiere auch häufiger Benutzerschnittstellen auf grafischen LCDs. Mit Hilfe der Polymorphie und Objekten, die sich selbst zeichnen, kann man da meiner Meinung nach viel übersichtlicher und flexibler seine Schnittstelle aufbauen. Insgesamt ist meine Erfahrung, dass einfache, aber dafür zeitkritische Steueraufgaben meist besser klassisch funktional/prozedural gelöst werden sollten. Bei zeitunkritischen Steueraufgaben mit Benutzerschnittstelle hat dafür der objektorientierte Ansatz Vorteile.
Detlev T. schrieb: > Hans-Georg Lehnard schrieb: >> Hmm ich spare bei dem Toggle Befehl mit dem Input Pin Register 2Byte im >> Flash und 2 Takte während der Laufzeit dafür handle ich mir etwas nicht >> portables in einer "portablen" Lib ein. > > Ganz so einfach ist das nicht, denn read-modify-write ist nicht atomar. > Hat man eine ISR, die einen anderen Pin an demselben Port ändert, geht > das irgendwann schief. Es sei denn, man denkt dran, das ganze mit > ATOMICBLOCK zu umschließen. Das ist natürlich richtig. Aber wie oft kommt es vor, daß ich auf ein und denselben Pin in der Main-Loop und einer ISR zugreife? > Zum eigentlichen Thema: > > Ich programmiere seit einiger Zeit meine XMegas objektorientiert. Das > hat mehrere Gründe: > > Erst einmal ist das für mich persönlich die Art, wie ich am besten > Programmieraufgaben löse. Das ist rein subjektiv und gilt natürlich > nicht allgemein. Doch, eigentlich schon. Wer einmal den Komfort von objektorientierter Programmierung kennengelernt hat, will ihn nicht mehr missen... ;-) > Bei mehrfach vorhandener, identischer Hardware (z.B. bis zu 8 USARTs > beim XMega) stösst die herkömmliche Programmierung mit einer festen > Kodierung der Hardware definitiv an ihre Grenzen. Eine dynamische > Bindung der Hardware an jeweils eine Instanz einer Klasse zur Laufzeit > ist da deutlich einfacher und flexibler. > > Ich programmiere auch häufiger Benutzerschnittstellen auf grafischen > LCDs. Mit Hilfe der Polymorphie und Objekten, die sich selbst zeichnen, > kann man da meiner Meinung nach viel übersichtlicher und flexibler seine > Schnittstelle aufbauen. > > Insgesamt ist meine Erfahrung, dass einfache, aber dafür zeitkritische > Steueraufgaben meist besser klassisch funktional/prozedural gelöst > werden sollten. Bei zeitunkritischen Steueraufgaben mit > Benutzerschnittstelle hat dafür der objektorientierte Ansatz Vorteile. Nunja... man kann auch in C sehr viele OO-Konzepte umsetzen, aber das ist dann eben nicht so übersichtlich. Elecia White hat da in "Making Embedded Systems" viele gute Anregungen und Tips. Auf Mikrocontrollern kann man es ja machen. ;-) Andererseits bin ich jedoch überzeugt, daß man mit einer guten C++-Library ein Werkzeug haben und viele komfortable Konzepte nutzen kann, ohne dabei an Effizienz gegenüber C einzubüßen. Natürlich kann man kritische Dinge in Assembler machen -- aber da sind C und C++ doch kein Hindernis?! Liebe Grüße, Karl
Karl Käfer schrieb: > Das ist natürlich richtig. Aber wie oft kommt es vor, daß ich auf ein > und denselben Pin in der Main-Loop und einer ISR zugreife? Nee, nee, nicht den selben Pin. Es reich(t|en) schon ein/ mehrere andere(r) Pin(s) am selben Port!
Wieder ein Beispiel für C++ auf dem MC: Beitrag "Nucleo STM32-F103RB nun wirklich "Arduino kompatibel"" Die Arduino Lib mit all ihren Unzulänglichkeiten hat sich ordentlich verbreitet. Der Vorteil: bekannte Funktionsnamen, viele Codebeispiele
Detlev T. schrieb: > Bei mehrfach vorhandener, identischer Hardware (z.B. bis zu 8 USARTs > beim XMega) stösst die herkömmliche Programmierung mit einer festen > Kodierung der Hardware definitiv an ihre Grenzen. Warum?
Detlev T. schrieb: > Bei mehrfach vorhandener, identischer Hardware (z.B. bis zu 8 USARTs > beim XMega) stösst die herkömmliche Programmierung mit einer festen > Kodierung der Hardware definitiv an ihre Grenzen. Eine dynamische > Bindung der Hardware an jeweils eine Instanz einer Klasse zur Laufzeit > ist da deutlich einfacher und flexibler. Richtig ist sicherlich, dass die Bindung der Hardware an jeweils eine Instanz einer Klasse besser lesbar ist:
1 | serial1.print(const char * str); |
Aber bevor nun wieder 'mit ANSI C geht das auch' kommt:
1 | serialprint(const SerialHnd * serial1, const char * str); |
Die herkömmliche Programmierung ist weniger 'schön' und Fehler des Programmierers rutschen schneller mal durch. chris_ schrieb: > Der Vorteil: bekannte Funktionsnamen, viele Codebeispiele Man sollte sich m.E. daran orientieren, wenn man was effizientes neu aufbauen möchte. Immer wird das sicher nicht gehen.
:
Bearbeitet durch User
Torsten C. schrieb: > Richtig ist sicherlich, dass die Bindung der Hardware an jeweils eine > Instanz einer Klasse besser lesbar ist Mit ldi ZL,low(Var) ;Pointer auf Variable im SRAM ldi ZH,high(Var) rcall serial1out ist auch alles ganz verständlich gesagt. Ganz ohne zusätzlichen Bedarf an Klassen und deren Instanzen. > Die herkömmliche Programmierung ist weniger 'schön' und Fehler des > Programmierers rutschen schneller mal durch Asm formuliert klar, einfach, eindeutig. Die schweren Fehler sind sprachunabhängig sowieso eher strategischer bzw. systemarchitektonischer Natur oder durch Nichttesten aller möglichen realen Inputs bedingt. Weniger schön finde ich wiederum > serial1.print(const char * str); und man weiß ja, das Instruktionen und Ausdrücke in C noch ganz andere, viel extremere Formen annehmen können.
Torsten C. schrieb: > Die herkömmliche Programmierung ist weniger 'schön' und Fehler des > Programmierers rutschen schneller mal durch. Um das zu verfeinern: Herkömmliche Programmierung (gilt für ANSI-C genau so wie für Assembler) erlaubt es nicht, dass unerlaubte Parameter-Typen als Parameter übergeben werden. Enums sind z.B. wie Integer. Es geht nicht nur um 'Fehler des Programmierers'^^, sondern auch um Bequemlichkeit: Die IDE schlägt nur Parameter, Methoden usw. vor, die auch compilierbar sind, also z.B. möglichst keine 'private' Member außerhalb der Klasse. Bei C# ist das z.B. so. Um nochmal den 'Guru' zu zitieren: "fundamental design goal: design violations should not compile". Die Steigerung wäre: "the IDE should warn at design violations". Das gehört eher zum Thema: "C++ auf einem MC, welche Vorteile hätte das?" Ich hoffe, es passt trotzdem!
:
Bearbeitet durch User
>Weniger schön finde ich wiederum >> serial1.print(const char * str); Das Serial.print ist polymorph: Serial.print(78) gives "78" Serial.print(1.23456) gives "1.23" Serial.print('N') gives "N" Serial.print("Hello world.") gives "Hello world." Serial.print(78, BIN) gives "1001110" Serial.print(78, OCT) gives "116" Serial.print(78, DEC) gives "78" Serial.print(78, HEX) gives "4E" Serial.println(1.23456, 0) gives "1" Serial.println(1.23456, 2) gives "1.23" Serial.println(1.23456, 4) gives "1.2346" Aus http://arduino.cc/en/serial/print
chris_ schrieb: > Das Serial.print ist polymorph Du meinst wahrscheinlich das richtige. Es geht Dir hier vermutlich nicht um Polymorphie mit der Bestimmung der Klasse zur Laufzeit. Das würde VTables und Performance-Nachteile bringen. Funktionen und Methoden mit gleichen Bezeichnern im gleichen Namensraum und unterschiedlichen Signaturen sind aber auch was, was die Lesbarkeit des Codes m.E. deutlich verbessert. Das sehe ich auch so, Chris. Der Vorteile von Namensräumen wurde ja auch schon genannt, wobei einige IDEs diesen Aspekt m.E. noch nicht perfekt im Griff haben.
:
Bearbeitet durch User
>Es geht Dir hier vermutlich nicht um Polymorphie mit der Bestimmung der >Klasse zur Laufzeit. Das würde VTables und Performance-Nachteile >bringen. Leider weiß ich nicht, ob Polymorphismus in C++ immer VTables nach sich zieht. Poylmorphie wird im englischen Wikpedia-Artikel an einem Python Beispiel gezeigt, indem es so etwas wie VTables vermutlich nicht gibt. Ich denke das ist eine Implementierungsdetail von C++ was aber genauso gut zur vom Compiler zur Compile-Time ohne VTables zu lösen wäre. http://en.wikipedia.org/wiki/Polymorphism_%28computer_science%29
Moby schrieb: > Asm formuliert klar, einfach, eindeutig. Bitte eröffne deinen eigenen Assembler-Evangelismus-Thread. Deine diesbezüglichen Auslassungen sind in diesem Thread unerwünscht.
> Serial.print(78) gives "78" > Serial.print(1.23456) gives "1.23" > Serial.print('N') gives "N" > Serial.print("Hello world.") gives "Hello world." >Es geht Dir hier vermutlich nicht um Polymorphie mit der Bestimmung der >Klasse zur Laufzeit. Nachdem ich mir den Wikipedia Artikel durchgelesen habe http://de.wikipedia.org/wiki/Tabelle_virtueller_Methoden bin ich der Meinung, dass für den obigen Code keine VTables gebraucht werden, da alle Argumente zur Laufzeit bekannt sind. Der Compiler kann z.B für Serial.print(1.23456) gives "1.23" gleich die passende Funktion Serial.print(float f) kompilieren. Ich nehme mal an und hoffe, dass es der gcc auch so macht.
Hallo chris_, chris_ schrieb: > bin ich der Meinung, dass für den obigen Code keine VTables gebraucht > werden, da alle Argumente zur Laufzeit bekannt sind. Der Compiler kann > z.B für > > Serial.print(1.23456) gives "1.23" > > gleich die passende Funktion > > Serial.print(float f) > > kompilieren. > Ich nehme mal an und hoffe, dass es der gcc auch so macht. Das ist korrekt. "Serial.print(float f)" hat eine andere Funktionssignatur als "Serial.print(char c)", und darum weiß der Compiler auch ohne VTables, was er aufrufen muß. Der Compiler ist übrigens die C++-Version des gcc. Liebe Grüße, Karl
>Das ist korrekt.
Hallo Karl,
das freut mich zu hören. Ich habe mich bis gerade eben noch nie so
richtig mit dem Thema VTables befasst. Der Begriff wird aber immer gerne
mal wieder fallen gelassen wie eine Art "Mysterium". Das richtige
Verständnis dafür scheint für viele schwierig.
Das scheint mir auch für den Wikipedia-Artikel zu gelten. Da steht:
"Die Tabelle virtueller Methoden (engl.: virtual method table oder
virtual function table, kurz VMT, VFT, vtbl oder vtable) ist ein Ansatz
von Compilern objektorientierter Programmiersprachen um dynamisches
Binden umzusetzen. Das ist unter anderem Grundvoraussetzung für
Vererbung und Polymorphie."
Eigentlich müsste es präzisser heisen
Das ist unter anderem Grundvoraussetzung für Vererbung wenn die Objekte
zur Compile-Zeit noch nicht bekannt sind.
Karl Käfer schrieb: > "Serial.print(float f)" hat eine andere Funktionssignatur > als "Serial.print(char c)" Genau. Das ist alles Richtig, danke. Ich wollte nur sagen, dass unterschiedliche Funktionssignaturen auch ohne Klassen, Polymorphie und VTables in C++ gehen, aber nicht in K&R-C, ASM, … Dann sind wir uns mal wieder einig. :-) Sorry, falls ich für Verwirrung gesorgt haben sollte.
:
Bearbeitet durch User
chris_ schrieb: > bin ich der Meinung, dass für den obigen Code keine VTables gebraucht > werden, da alle Argumente zur Laufzeit bekannt sind. chris_ schrieb: > Leider weiß ich nicht, ob Polymorphismus in C++ immer VTables nach sich > zieht. Ich habe mir in C ein Interface / Vtable selber geschrieben. Da sieht man gut, wie der Compiler optimier kann und wann du Overhead hast.
1 | typedef struct { |
2 | void (*doStart)(int ID); |
3 | void (*doData)(const void* Data, int Length); |
4 | void (*doEnd)(void); |
5 | } Base_vtable_t; |
6 | |
7 | int Param[200]; //Daten die das Gerät hat |
8 | |
9 | /* Implementierung von UartSendStart */ |
10 | /* Implementierung von UartSendData */ |
11 | /* Implementierung von UartSendEnd */ |
12 | /* Implementierung von UartReceiveStart */ |
13 | /* Implementierung von UartReceiveData */ |
14 | /* Implementierung von UartReceiveEnd */ |
15 | |
16 | const Base_vtable_t sendData ={ UartSendStart, UartSendData, UartSendEnd}; |
17 | const Base_vtable_t receiveData = { UartReceiveStart, UartReceiveData, UartReceiveEnd}; |
18 | |
19 | void processPaket1(const Base_vtable_t *ProcType){ |
20 | ProcType->doStart( 100); |
21 | ProcType->doData(&(Param[100]), sizeof(Param[100])); |
22 | ProcType->doData(&(Param[110]), sizeof(Param[110])); |
23 | ProcType->doEnd(); |
24 | } |
25 | |
26 | void processPaket2(const Base_vtable_t *ProcType){ |
27 | ProcType->doStart( 100); |
28 | ProcType->doData(&(Param[2]), sizeof(Param[2])); |
29 | ProcType->doEnd(); |
30 | } |
31 | |
32 | int main (void){ |
33 | processPaket1( &sendData ); //Datenpaket senden |
34 | processPaket2( &receiveData ); //Datenpaket auslesen |
35 | } |
Was passiert: a) Compiler inlined processDataId100 mehr Code, dafür ist der Funktionszeiger auf doStart bekannt und die Vtable wird wegoptimiert. Der Code von doStart, etc. kann geinlined werden. b) Compiler lässt processDataId100 stehen die Funktion kennt die Funktionszeiger nicht. Der Zeiter wird aus der Vtable geladen. Statt einer Konstanten wird der Inhalt der Vtable als Sprungadresse gelesen. Der Code von doStart, etc. ist unbekannt und kann nicht optimiert werden. Wie man sieht liegt der Nachteil von Polymorphismus zur Laufzeit in der mangelnden Optimierungsmöglichkeit, wenn er nicht aufgelöst wird. Trivialen Funktionsaufrufe wie doEnd(), können nicht wegoptimiert/geinlined werden. Im Vergleich hat die Zeigerarithmetik meist weniger Aufwand. Dafür entsteht weniger Code, falls doEnd(), etc sowieso nicht geinlined werden (große Funktionen). in C++ müsste das irgendwie so aussehen:
1 | struct Base{ |
2 | virtual void doStart(int ID)=0; |
3 | virtual void doData(const void * Data, int length)=0; |
4 | virtual void doEnd()=0; |
5 | } |
6 | |
7 | struct sendData_t: public Base {/*Implementierung*/}; |
8 | sendData_t sendData; |
9 | struct receiveData_t: public Base{/*Implementierung*/}; |
10 | receiveData_t receiveData; |
11 | |
12 | void processPaket1(const Base * ProcType){ |
13 | ProcType->doStart( 100); |
14 | ProcType->doData(&(Param[100]), sizeof(Param[100])); |
15 | ProcType->doData(&(Param[110]), sizeof(Param[110])); |
16 | ProcType->doEnd(); |
17 | } |
18 | |
19 | int main (void){ |
20 | processPaket1( &sendData ); //Datenpaket senden |
21 | processPaket2( &receiveData ); //Datenpaket auslesen |
22 | } |
PS: Dieselbe Funktion nehmen zu können, um Datenpakete zu zusammenzustellen und wieder aufzudröseln, habe ich von BOOST (ich glaube serialize) geklaut. PSS: Man sollte im C++ Beispiel auch nicht mehr C-Arrays nehmen, sondern std::array http://de.cppreference.com/w/cpp/container/array Grüße Felix
FelixW schrieb: > Man sollte im C++ Beispiel auch nicht mehr C-Arrays nehmen, sondern > std::array Guter Hinweis, danke. :-) FelixW schrieb: > Wie man sieht liegt der Nachteil von Polymorphismus zur Laufzeit in der > mangelnden Optimierungsmöglichkeit, wenn er nicht aufgelöst wird. Ich habe keine zig verschiedenen Toolketten im Betrieb, um die Assembler-Dateien zu analysieren. Macht es Sinn, in einer C++-Lib Polymorphismus zu verwenden, der sich zur Compiler-Zeit auflösen lässt, oder ist das zu 'unsicher'?
Beim Begriff "Polymorphismus" scheint es auch ein unterschiedliches Verständnis zu geben. Bei den oben genannten Funktionenen > Serial.print(78) gives "78" > Serial.print(1.23456) gives "1.23" > Serial.print('N') gives "N" > Serial.print("Hello world.") gives "Hello world." geht es um den in der Wikipedia als "Ad hoc polymorphism" bezeichneten Typ: http://en.wikipedia.org/wiki/Polymorphism_%28computer_science%29 Felix, wenn ich Dein Beispiel richtig verstehe, geht es Dir um den als "Subtyping" bezeichneten Typ.
Torsten C. schrieb: > FelixW schrieb: >> Man sollte im C++ Beispiel auch nicht mehr C-Arrays nehmen, sondern >> std::array > Guter Hinweis, danke. :-) Das ist nur ein wrapper für c arrays, Der die Initialisierung, etc. unnötig erschwert. > Polymorphismus zu verwenden, der sich zur Compiler-Zeit auflösen lässt, > oder ist das zu 'unsicher'? Einfach die Basisklasse ausschlieslich verwenden, um davon klassen abzuleiten, und kein virtual verwenden. Dann kann nichts passieren.
Daniel A. schrieb: > Einfach die Basisklasse ausschlieslich verwenden, um davon klassen > abzuleiten, und kein virtual verwenden. Dann kann nichts passieren. Häää? Und dann werden die Member der abgeleiteten Klassen verwendet? Falls das geht, würde ich sehr gern lernen, wie das geht.
:
Bearbeitet durch User
Torsten C. schrieb: > Daniel A. schrieb: >> Einfach die Basisklasse ausschlieslich verwenden, um davon klassen >> abzuleiten, und kein virtual verwenden. Dann kann nichts passieren. > > Häää? Und dann werden die Member der abgeleiteten Klassen verwendet? > > Falls das geht, würde ich sehr gern lernen, wie das geht. Du gibst dem compiler einfach keinen grund für ne vtable
1 | //ungetestet
|
2 | class base { |
3 | public:
|
4 | inline int test(){ |
5 | return 0; |
6 | }
|
7 | };
|
8 | class derived : public base { |
9 | inline int test(){ |
10 | return 1; |
11 | }
|
12 | };
|
13 | |
14 | void printDerived(derived* x){ |
15 | std::cout << x.test() << std::endl; // muss derived::test sein, weil nicht virtual |
16 | }
|
17 | |
18 | // dashier vermeiden:
|
19 | // die Basisklasse ausschlieslich verwenden, um davon klassen abzuleiten
|
20 | void printBase(base* x){ |
21 | std::cout << x.test() << std::endl; // muss base::test sein, weil nicht virtual |
22 | }
|
23 | |
24 | int main(){ |
25 | derived x; |
26 | printDerived(x); // gibt 1 aus |
27 | printBase(x); // gibt 0 aus, weil nicht virtual |
28 | }
|
Die Polymorphie ist so leicht eingeschränkt, aber zur compiletime stehen alle Funktionsaufrufe fest, der compiler kann nichts in eine vtable packen. Die programmierung wird so aber anspruchsfoller.
chris_ schrieb: > Felix, wenn ich Dein Beispiel richtig verstehe, geht es Dir um den als > "Subtyping" bezeichneten Typ. ja Daniel A. schrieb: > Das ist nur ein wrapper für c arrays, Der die Initialisierung, etc. > unnötig erschwert. warum? Was geht nicht/nur umständlicher (bei welcher C++ Version). Daniel A. schrieb: >> Polymorphismus zu verwenden, der sich zur Compiler-Zeit auflösen lässt, >> oder ist das zu 'unsicher'? > Einfach die Basisklasse ausschlieslich verwenden, um davon klassen > abzuleiten, und kein virtual verwenden. Dann kann nichts passieren. Wenn der Compiler den bekannten Typ optimieren kann macht es keinen Unterschied. Wenn nicht, dass hast du nicht funktionierenden code. Ich rate davon ab! Irgendwo weiter vorne gab es ein PDF. Es gibt (subtyping) Polymorphismus zu a) Kompilierzeit (mit templates) b) Linkerzeit (mit templates und Instanzen) c) Laufzeit (virtual) chris_ schrieb: > Beim Begriff "Polymorphismus" scheint es auch ein unterschiedliches > Verständnis zu geben. > > Bei den oben genannten Funktionenen > >> Serial.print(78) gives "78" >> Serial.print(1.23456) gives "1.23" >> Serial.print('N') gives "N" >> Serial.print("Hello world.") gives "Hello world." > > geht es um den in der Wikipedia als "Ad hoc polymorphism" bezeichneten > Typ: > > http://en.wikipedia.org/wiki/Polymorphism_%28computer_science%29 Danke für die Klarstellung Vielleicht hilft dem besseren Verständnis: Ad-hoc Polymorphismus heißt nichts anderes, als das der Kompiler die Typen in den Funktionsnamen aufnimmt und damit keinen extra code generiert. (genauso wie namespace) Die genaue Namensgebung ist vom compiler abhängig. Ist Serial.print() nicht als "static" deklariert sieht das dann so aus: _Serial_print_int( &Serial, 78) _Serial_print_float( &Serial, 1.23456) _Serial_print_char( &Serial, 'N') _Serial_print_pointer_to_char( &Serial, "Hello world.") Damit kann man dann auch C++ Memberfunktionen von C-Code aus aufrufen (allerdings dann Kompilerabhängig).
Hallo FelixW, FelixW schrieb: > Ad-hoc Polymorphismus heißt nichts anderes, als das der Kompiler die > Typen in den Funktionsnamen aufnimmt und damit keinen extra code > generiert. Der Compiler übernimmt nur die Argument-Typen in die Signatur, nicht die Rückgabe-Typen. Daher ist:
1 | bool print(int); |
2 | int print(int); |
ein Fehler. Liebe Grüße, Karl
FelixW schrieb: > Wenn der Compiler den bekannten Typ optimieren kann macht es keinen > Unterschied. Genau da liegt dass Problem: Wenn mann bei meinem Beispiel derived::test virtual machen würde, kann der Compiler zur Compiletime nicht wissen, ob in einer anderen Compilationsunit eine classe von derived abgeleitet wird, die die methode test überschreibt. Dass ist erst zur linkerzeit bekannt, wenn keine pointer auf die Classen bei dynamisch nachgeladenen Library Funktionen als Parameter oder Rückgabewerte existieren, wobei dass der Compiler nicht wissen kann. Derartige Compileroptimierungen halte ich für unwarscheinlich. > Wenn nicht, dass hast du nicht funktionierenden code. Diese aussage ist falsch. Es verhält sich nicht so wie bei gewönlicher polymorphie, aber es funktioniert, so wie es im standard definiert wurde. > Ich rate davon ab! Wenn alle beteiligten die Funktionsweise von Code ohne virtual verstehen, und wenn sie wissen, was sie tuhen, sehe ich keine Probleme.
Hallo Daniel, Daniel A. schrieb: > Dass ist erst zur linkerzeit bekannt, "-flto" ist unser Freund. ;-) SCNR, Karl
Ich habe den Thread interessiert mitgelesen und im Netz eine Library von KonstantinChizhov gefunden, die für meine Kenntnisse noch einigermassen nachvollziehbar ist. Alexandra Mcucpp - is a C++ hardware abstraction library for microcontrollers. ************************************************************************ The aim of the project is to make a high level but extremly efficient C++ hardware abstraction framework for different microcontroller families. For latest version visit https://github.com/KonstantinChizhov/Mcucpp ************************************************************************
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.