Gefunden wurde der Fehler von einem hiesigen Forenmitglied.
Und das in/mit meinem Code...
(Eine Blamage?)
Leider kann ich nicht sehen, was ich dort falsch mache.
Ich möchte euch bitten, das zu testen, und mir zu sagen, was ich falsch
mache, oder ob das ein Compiler Bug ist.
Minimalisierter Testcode im Anhang.
Testbedingung:
Compiler Gnu avr-c++ unter win10
Getestete Versionen: 5.4 7.3 8.2 9.1
Einstellung gnu++11 oder höher
Sowohl mit Arduino IDE, als auch mit Atmel Studio.
Die Kompilation läuft mit allen Compilerversionen für den ATMega328P und
ATMega32U4 sauber durch, und funktioniert auch alles.
Die Compiler Varianten 8.2 und 9.1 versagen an dem Array für den
ATMega2560 mit folgender Meldung:
Das entfernen des Wortes "constexpr" würde das Problem ausmerzen, ist
dann aber mit ungewolltem Speicherverbrauch zur Laufzeit verbunden
Wer hat den Lattenschuss?
Ich oder der Compiler?
Was habe ich übersehen?
Arduino Fanboy D. schrieb:> Die Compiler Varianten 8.2 und 9.1 versagen
... versagen nicht, sondern implementieren den Standard lediglich
rigoroser. Die alten Versionen waren nachlässiger.
Die "PORTxx" sind Makros welche einen C-Style-Cast der Art "(volatile
uint8_t*) 0x1234" enthalten. Dieser wird vom C++-Compiler als
"reinterpret_cast<volatile uint8_t*> (0x1234)" verstanden (named cast),
daher die Ausgabe. Ein solches reinterpret_cast ist aber in
constexpr-Kontexten verboten.
Ein Workaround ist es, die Pointer-Adressen als uintptr_t ins Array zu
speichern (ohne cast), und dann beim Zugriff erst das reinterpret_cast
zu machen.
Arduino Fanboy D. schrieb:> Ich oder der Compiler?
Der Code ist laut Standard fehlerhaft :-)
Vincent H. schrieb:> In C++20 wird es dann möglich sein deine "portList" mit Hilfe von> std::bit_cast (https://en.cppreference.com/w/cpp/numeric/bit_cast) zu> erzeugen.
Da steht aber:
This function template is constexpr if and only if each of To, From and
the types of all subobjects of To and From:
- is not a pointer type;
Niklas G. schrieb:> Ein Workaround ist es, die Pointer-Adressen als uintptr_t ins Array zu> speichern (ohne cast), und dann beim Zugriff erst das reinterpret_cast> zu machen.
Werde ich untersuchen.
---------------------
Niklas G. schrieb:> ... versagen nicht, sondern implementieren den Standard lediglich> rigoroser. Die alten Versionen waren nachlässiger.
Wenn das der Punkt wäre, dann müssten 8.2 und 9.1 auch bei den 328P und
32U4 Arrays erbost aufschreien. Denn es ist ja exakt das gleiche
Prinzip.
*Tun sie aber nicht!*
Das geht ohne Murren durch und funktioniert auch.
--------------------------
Niklas G. schrieb:> Ein solches reinterpret_cast ist aber in> constexpr-Kontexten verboten.
Da die Arrays in constexpr Funktionen ausgewertet werden, wäre somit
auch dort der reinterpret_cast, deiner Ansage nach, ebenfalls verboten.
Das wäre dann der Todesstoß für das ganze Vorhaben.
Arduino Fanboy D. schrieb:> Das geht ohne Murren durch und funktioniert auch.
Dann funktioniert das bei denen irgendwie ohne Cast. Such doch mal die
Definition von PORTxx bei den unterschiedlichen Controllern raus...
Arduino Fanboy D. schrieb:> Da die Arrays in constexpr Funktionen ausgewertet werden, wäre somit> auch dort der reinterpret_cast, deiner Ansage nach, ebenfalls verboten.
Richtig.
Arduino Fanboy D. schrieb:> Das wäre dann der Todesstoß für das ganze Vorhaben.
Es sei denn du speicherst im Array die Adresse als Zahl statt des
Pointers, und castest erst ganz zum Schluss, wenn tatsächlich etwas auf
den Port geschrieben wird. Oder du speicherst im Array nur Port-Nummern
(A->0, B->1, C->2), welche dann auch schön in 8 bit passen, und mappst
das erst zum Schluss durch eine Look-Up-Table auf die
Peripherie-Register-Adressen, welche dann erst auf den Pointer-Typ
gecastet werden. Beim AVR sind die Register ja leider nicht regelmäßig
angeordnet und aus der Nummer berechenbar...
Niklas G. schrieb:> Vincent H. schrieb:>> In C++20 wird es dann möglich sein deine "portList" mit Hilfe von>> std::bit_cast (https://en.cppreference.com/w/cpp/numeric/bit_cast) zu>> erzeugen.>> Da steht aber:>> This function template is constexpr if and only if each of To, From and> the types of all subobjects of To and From:> - is not a pointer type;
F.uck -.-
Das mit den Pointern les ich zum ersten mal. Wahhh. Eigentlich wär
bit_cast in meinen Augen DAS C++20 Feature für Embedded gwesen... Damit
hätte man endlich die ganzen SVD generierten Header sinnvoll nutzen
könne können. :(
Niklas G. schrieb:> Dann funktioniert das bei denen irgendwie ohne Cast. Such doch mal die> Definition von PORTxx bei den unterschiedlichen Controllern raus...
Das habe ich schon hinter mir....
Das sieht identisch aus.
(ja, die Zahlen/Adressen unterscheiden sich, aber sonst nix)
-> Ja, das untersuche ich nochmal!
Auch der Code nach dem Preprozessor sieht gut aus.
Nicht schön, aber bei allen versuchten Compiler und io*.h Versionen
identisch
Der Preprozessor ist also auch unschuldig.
-> Auch das untersuche ich nochmal!
Niklas G. schrieb:> Es sei denn du speicherst im Array die Adresse als Zahl statt des> Pointers, und castest erst ganz zum Schluss, wenn tatsächlich etwas auf> den Port geschrieben wird. Oder du speicherst im Array nur Port-Nummern> (A->0, B->1, C->2), welche dann auch schön in 8 bit passen, und mappst> das erst zum Schluss durch eine Look-Up-Table auf die> Peripherie-Register-Adressen, welche dann erst auf den Pointer-Typ> gecastet werden. Beim AVR sind die Register ja leider nicht regelmäßig> angeordnet und aus der Nummer berechenbar...
Ja, vielleicht muss so ein Workaround her um das Problem zu umgehen.
Nur bleibe dann die Frage offen, ob das ein Compiler Bug ist...
(welcher dann irgendwann evtl. behoben wird)
Um sowas möchte ich nicht gerne drumrum arbeiten.
Es wäre dann schöner, wenn die Ursache behoben wird.
Arduino Fanboy D. schrieb:> Nur bleibe dann die Frage offen, ob das ein Compiler Bug ist...
Es ist wenn schon ein Compiler/Library Bug wenn es kompiliert. Der
Standard deklariert solchen Code als falsch. Vielleicht "funktioniert"
es auf den kleinen Cobtrollern nur aufgrund irgendwelcher AVR
Besonderheiten im Compiler - korrekt ist es nicht. Bei Cortex-M geht es
jedenfalls mit aktuellem GCC auch nicht, mit alten Versionen schon
(fälschlicherweise).
Einen Unterschied kann ich jetzt erkennen!
Hier ein Auszug aus der iomxx0_1.h, welche für den Mega2560 eingebunden
wird.
#define PORTB _SFR_IO8(0x05)
#define PORTH _SFR_MEM8(0x102)
Einträge mit _SFR_IO8() gehen durch.
Einträge mit _SFR_MEM8() werden angemäckert.
...
In dem Punkt sind alle Versionen der iomxx0_1.h gleich, jeder Compiler
bringt ja seine eigene mit.
Arduino Fanboy D. schrieb:> Einträge mit _SFR_IO8() gehen durch.> Einträge mit _SFR_MEM8() werden angemäckert.
Ah, wahrscheinlich werden "MEM" Adressen als "normale" Pointer über den
16bit-Adressraum verwendet (LD, ST) und die "IO" Adressen über den
IO-Adressraum (IN, OUT), und bei letzterem hat der GCC sowieso eine
Sonderbehandlung.
Niklas G. schrieb:> und bei letzterem hat der GCC sowieso eine> Sonderbehandlung.
Das ist wahr.
Aber die nötige Information wird nicht aus den Macros bereit gestellt.
Zumindest nicht anhand des Datentypes, der ist bei beiden Varianten
gleich, oder?
Auszug aus der sfr_defs.h
Arduino Fanboy D. schrieb:> Aber die nötige Information wird nicht aus den Macros bereit gestellt.> Zumindest nicht anhand des Datentypes,
Kann sein, kenne mich mit AVR nicht so gut aus. Wie gesagt ist der Code
so nicht korrekt, du kommst um eine Anpassung früher oder später nicht
herum...
Niklas G. schrieb:> du kommst um eine Anpassung früher oder später nicht> herum...
Sei es drum....
Selbst die Anpassung will nicht gelingen!
Also lautet meine Frage jetzt "Wie tun?"
---------
Der vorherigen Idee gefolgt:
Versagt mit der bekannten Meldung.
> error: reinterpret_cast from integer to pointer
Auch so bekomme ich die Adressen/Pointer nicht ins Array.
----
Es ist auch nicht so, dass der explizite Cast selber ein Problem
darstellt, hier ein extrem:
1
constexprintportList[]={(int)&PORTB};// geht durch
2
constexprintportList[]={(int)&PORTH};// Error
Auch wieder:
> error: reinterpret_cast from integer to pointer
Arduino Fanboy D. schrieb:> Auch so bekomme ich die Adressen/Pointer nicht ins Array.
Korrekt, du musst du Adressen leider von Hand eintippen, wenn in den
AVR-Headern nicht irgendwo Makros mit den Adressen stehen. Zwei
reinterpret_cast (bzw. (uintptr_t) - was das Gleiche ist) hintereinander
sind auch nicht besser als eins.
So ala:
Arduino Fanboy D. schrieb:> constexpr int portList[] = {(int)&PORTB}; // geht durch
Wie gesagt eigentlich auch falsch, die GCC-Sonderbehandlung von
IO-Adressen kleiner 0x1F ist wohl fälschlicherweise nachlässig.
Da anscheinend zumindest beim ATmega64/128/256 die PIN-DDR-PORT Register
doch einem gewissen Muster folgen, könnte man es vielleicht noch so
schöner machen:
Niklas G. schrieb:> Korrekt, du musst du Adressen leider von Hand eintippen, wenn in den> AVR-Headern nicht irgendwo Makros mit den Adressen stehen.
Das wäre sehr unschön!
Denn es zieht weitreichende Umbauten nach sich.
Es sind ja nicht nur die PORTX betroffen, sondern auch DDRX und PINX
Und das dann für eine ganze Latte an AVRs
Es wäre mir deutlich lieber, die schon in den Headern vorhandenen
Adressen nutzen zu können.
--
Aber einen Vorteil hat es ja, es läuft mit allen Compiler Versionen,
welche ich hier zum testen habe.
Im Anhang eine Testversion. Ist noch ein Provisorium
Die Template Funktion findet sich später in einer Klasse wieder
----------------
Niklas G. schrieb:> Da anscheinend zumindest beim ATmega64/128/256 die PIN-DDR-PORT Register> doch einem gewissen Muster folgen, könnte man es vielleicht noch so> schöner machen:
Werde ich untersuchen!
Zugreifen kann man dann wie bei einem Array, allerdings hat man
Referenzen und keine Zeiger. Kommt drauf an, was du mit dem Array dann
machen willst.
Nachteil bei solchen constexpr ist aber immer, dass sie Speicher
(.rodata) schlucken, wenn nicht alles zur Compilezeit aufgelöst werden
kann wie im Beispiel.
Johann L. schrieb:> Nachteil bei solchen constexpr ist aber immer, dass sie Speicher> (.rodata) schlucken, wenn nicht alles zur Compilezeit aufgelöst werden> kann wie im Beispiel.
Ja, sowas ist fatal.
Im Grunde möchte ich folgendes realisieren....
1. kein Ram verbrauch, für diesen Kram
2. keine Arrays im Flash
3. Arduino Pin Nummerierung verwenden
4. ein schlichtes/naives OOP artiges Interface
5. möglichst hohe Geschwindigkeit, zur Laufzeit.
Zur Compilezeit darf der Compiler ruhig derbe ackern.
In den Anhang packe ich mal eine funktionierende Version.
Eine Arduino typische Library.
Ist noch nicht die Ausgeburt aller Schönheit, ich weiß....
Halt noch eine Baustelle.
Wahrscheinlich widerspricht es der Arduino-Philosophie, aber der Trick
besteht darin, statt Werten (Pointer) Typen zu benutzen (wie
std::integral_constant). Bei Typen ist man sich immer sicher, dass sie
im Maschinencode ja nicht mehr existieren.
Also statt einer Liste von Zeigerwerten der Ports/Pins, eine Liste von
Typen, die das repräsentieren. Die Auflösung zu einer Adresse wird dann
genau dort gemacht, wo man sie braucht, also ggf. in einer
Elementfunktion. Dann wird alles schön wegoptimiert. (Irgendwo hier im
Forum stecken auch meine Beispiele dazu, ca. aus dem Jahr 2017 oder
davor).
Johann L. schrieb:> Vielleicht so?
Sicher dass das funktioniert? Da sind doch wieder die PORTx-Makros inkl.
Cast in einem contexpr-Kontext.
Johann L. schrieb:> obwohl> es wegoptimiert werden könnte:
Kann es ja nicht, weil man durch den nicht-inline Funktionsaufruf
erzwingt, dass die Instanz angelegt wird...
Arduino Fanboy D. schrieb:> 2. keine Arrays im Flash> 5. möglichst hohe Geschwindigkeit, zur Laufzeit.
Das geht halt nur, wenn dem Compiler an jeder Stelle der Pin bekannt
ist. So etwas wie mein Beispiel (Iteration)
Niklas G. schrieb:> for (auto & p : pinList) {> p.writeOut ();> p.setOut (true);> }
passt damit nicht zusammen, weil hier die Pin-Nummern erst zur Laufzeit
bekannt und variabel sind.
Niklas G. schrieb:> Das geht halt nur, wenn dem Compiler an jeder Stelle der Pin bekannt> ist.
Ja, so ist es jetzt bei mir realisiert. (siehe Zip Anhang)
(war es vorher auch schon, da haben mir nur die neueren Compiler in den
Brei gespuckt)
Wilhelm M. schrieb:> Wahrscheinlich widerspricht es der Arduino-Philosophie, aber der Trick> besteht darin, statt Werten (Pointer) Typen zu benutzen (wie> std::integral_constant). Bei Typen ist man sich immer sicher, dass sie> im Maschinencode ja nicht mehr existieren.
Die "Arduino-Philosophie" kann mich mal....
Einzig die Pin Nummerierung muss ich übernehmen, weil sie auf den Boards
aufgedruckt ist, und jeder Arduino Jünger sie so erwartet.
Da steckt der Kompromiss, welchen ich eingehen muss.
Daher auch der Zwang zu solchen Arrays.
> (wie std::integral_constant)
Leider keine STD Lib
Leider auch keine STL
Nur die C-Lib
Wilhelm M. schrieb:> (Irgendwo hier im> Forum stecken auch meine Beispiele dazu, ca. aus dem Jahr 2017 oder> davor).
Ich meine mich schwach zu erinnern....
Was das nicht ein riesen Template Berg?
Finde es aber gerade nicht wieder.....
Wenn mich meine Erinnerung nicht trügt war die Benutzung in etwa so:
setPin(led); // um eine LED einzuschalten.
Das gefällt mir nicht so...
Ich hätte es gerne so:
led.setHigh();
oder
led = 1;
oder
led.set(HIGH)
oder
led(1)
Das ermöglicht eine recht simple Notation in der Anwendung.
setPin(led); Hier erscheint die led passiv, mit ihr wird etwas
angestellt
led.set(HIGH) Hier erhält die LED einen Auftrag: Mache dich an
Ok, das ist nur ein eher philosophischer Unterschied, und sollte sich
nicht im generierten Code nieder schlagen.
Ein Beispiel:
(8 und 3 sind die Arduino Pin Nummern)
1
// Retriggerbares Monoflop / Flankenverzögerer
2
#include<CombiePin.h>
3
#include<CombieTimer.h>
4
5
Combie::Pin::TasterGND<8>taster;// Taster schaltet gegen GND
6
Combie::Pin::OutputPin<3>led;
7
Combie::Timer::FallingEdgeTimerfallingEdge(200);// ms Nachleuchtdauer
8
9
voidsetup()
10
{
11
taster.initPullup();
12
led.init();
13
}
14
15
voidloop()
16
{
17
18
// Datenfluss Schreibweise
19
led=fallingEdge=taster;
20
21
/*
22
// Functor Schreibweise
23
led(fallingEdge(taster()));
24
25
26
// Traditionelle Methodenaufruf Schreibweise
27
led.set(fallingEdge.doTrigger(taster.pressed()));
28
*/
29
}
Wie du siehst, sind mir die Arduino Traditionen nicht ganz so
wichtig....
Wilhelm M. schrieb:> Bei Typen ist man sich immer sicher, dass sie> im Maschinencode ja nicht mehr existieren.
Richtig!
Ist bei mir so.
Irgendwann müssen die Pins ja gesetzt werden, das findet sich dann im
Code.
z.B. led.toggle() reduziert sich auf 1 ASM Statement.
(wenn der Pin im IO Bereich liegt)
Wilhelm M. schrieb:> Also statt einer Liste von Zeigerwerten der Ports/Pins, eine Liste von> Typen, die das repräsentieren.
?
Niklas G. schrieb:> Das ginge so. Es haben dann halt alle Pins einen anderen Typ und sind> nicht (ohne template) an Funktionen zu übergeben oder iterierbar.
Statisch iterierbar ist trivial:
1
usingled1=Combie::Pin::OutputPin<3>;
2
usingled2=Combie::Pin::OutputPin<4>;
3
4
template<typenameT>structList{};
5
6
usinglistOfLeds=List<led1,led2>;
7
8
template<typename...Leds>
9
voidfoo(List<Leds...>){
10
(Leds::on(),...);
11
}
12
13
voidbla(){
14
[]<typenameLeds>(List<Leds...>){
15
(Leds::toggle(),...);
16
}(List<led1,led2>{]);
17
18
}
19
20
voidtest(){
21
foo(listOfLeds{});
22
bla();
23
}
Viele Varianten von dem obigen sind denkbar.
Dynamisch geht aber auch (Sichwort Visitor).
Es macht keinen Sinn, dafür (Leds) zur Laufzeit Objekte zu erzeugen.
Denn es ist einfach falsch, zur Laufzeit eine beliebige Anzahl von
Led-Objekten zu erzeugen. Das macht man statisch, dann kann man zur
Compilezeit wesentlich mehr prüfen.
Niklas G. schrieb:> struct integral_constant
Werde ich mir näher anschauen/üben.
Niklas G. schrieb:> Es haben dann halt alle Pins einen anderen Typ und sind> nicht (ohne template) an Funktionen zu übergeben oder iterierbar.
Mit dem iterieren, das ist der am ehesten fühlbare Nachteil.
Ist allerdings auch erstaunlich selten nötig....
Wilhelm M. schrieb:> Viele Varianten von dem obigen sind denkbar.
Wird auch untersucht.
Wilhelm M. schrieb:> Statisch iterierbar ist trivial:
Klar. Das erzeugt aber ggf. eine Menge Code.
Wilhelm M. schrieb:> Dynamisch geht aber auch (Sichwort Visitor).
Das ist nur statisch-auf-dynamisch adaptiert und hat u.U. das selbe
Problem.
Wilhelm M. schrieb:> Es macht keinen Sinn, dafür (Leds) zur Laufzeit Objekte zu erzeugen.
Wenn man einen Pin an eine Funktion (z.B. für Soft-UART o.ä.) übergeben
möchte, muss der Parameter zur Laufzeit zur Verfügung stehen und
erstellt werden; es sei denn die Funktion ist ein template, was wieder
bei Mehrfachnutzung u.U. viel Code produzieren könnte.
"Echte" Instanzen von Pins anzulegen, die dann "static constexpr" sind
und wegoptimiert werden, bieten den Vorteil einer intuitiveren Nutzung,
mit pin.set statt MyPin::set.
Arduino Fanboy D. schrieb:> Ist allerdings auch erstaunlich selten nötig....
Ich hatte mal ein LCD mit 24 Pins, da ist es ganz nett das mit einer
Schleife machen zu können. War aber auch ein Prozessor mit vernünftiger
Register-Adressierung, sodass die Schleife nur ein bisschen
Pointer-Magie machen muss.
Niklas G. schrieb:> Wilhelm M. schrieb:>> Statisch iterierbar ist trivial:>> Klar. Das erzeugt aber ggf. eine Menge Code.>> Wilhelm M. schrieb:>> Dynamisch geht aber auch (Sichwort Visitor).>> Das ist nur statisch-auf-dynamisch adaptiert und hat u.U. das selbe> Problem.
Die ganze template-bloat-Problematik wird gewaltig überschätzt. In der
Praxis ist das aus m.E. nicht relevant.
>> Wilhelm M. schrieb:>> Es macht keinen Sinn, dafür (Leds) zur Laufzeit Objekte zu erzeugen.>> Wenn man einen Pin an eine Funktion (z.B. für Soft-UART o.ä.) übergeben> möchte, muss der Parameter zur Laufzeit zur Verfügung stehen
Nein, warum? Ander der Pin sich auf der Hardware?
> erstellt werden; es sei denn die Funktion ist ein template, was wieder> bei Mehrfachnutzung u.U. viel Code produzieren könnte.
Alles ist ein template ;-)
> "Echte" Instanzen von Pins anzulegen, die dann "static constexpr" sind> und wegoptimiert werden, bieten den Vorteil einer intuitiveren Nutzung,> mit pin.set statt MyPin::set.
Würde ich nicht sagen. Die klassische (Laufzeit-) Objektorientierung ist
nur ein Programmierparadigma von vielen.
>> Arduino Fanboy D. schrieb:>> Ist allerdings auch erstaunlich selten nötig....>> Ich hatte mal ein LCD mit 24 Pins, da ist es ganz nett das mit einer> Schleife machen zu können.
Wie gesagt, auch das geht ja.
Das Wichtigste m.E.: wer etwas zur Laufzeit mach, kann nur zur Laufzeit
prüfen, wer etwas statisch macht, kann zur Compilezeit prüfen.
Arduino Fanboy D. schrieb:> Ich meine mich schwach zu erinnern....> Was das nicht ein riesen Template Berg?> Finde es aber gerade nicht wieder.....Beitrag "Re: AVR GPIOR Bit Verwaltung C++">wie std::integral_constant)>Leider keine STD Lib>Leider auch keine STL>Nur die C-Lib
example.tgz: \include\std\... std::integral_constant in type_traits
>Die "Arduino-Philosophie" kann mich mal....>Einzig die Pin Nummerierung muss ich übernehmen, weil sie auf den Boards>aufgedruckt ist, und jeder Arduino Jünger sie so erwartet.>Da steckt der Kompromiss, welchen ich eingehen muss.>Daher auch der Zwang zu solchen Arrays.
Das heisst doch eigentlich, dass ein Interface fehlt, um eine
typbasierte c++Port/Pin Funktionalität für Arduino zur Verfügung zu
stellen !?
A. B. schrieb:> Das heisst doch eigentlich, dass ein Interface fehlt, um eine> typbasierte c++Port/Pin Funktionalität für Arduino zur Verfügung zu> stellen !?
Ein wenig habe ich Schwierigkeiten die Frage(?) zu verstehen.
Aber ich versuche zu antworten.
Klar, gibts ein Arduino eigenes Framework um mit Pins umzugehen.
Unabhängig von µC Type und Hersteller.
Funktionsfähig in C++, C und auch aus *.S (ASM) nutzbar.
Das Konzept hat seine Vorteile, ist aber (oft) recht lahm und Ressourcen
fressend. (gerade bei den AVR macht sich das bemerkbar)
Für viele der 32 Bit System gibts alles, STD-Lib, STL und auch
Exceptions.
In Sachen AVR ist die Luft deutlich dünner
Das ist eine Einschränkung auf Grund des gnu AVR-C++ Lieferumfangs,
keine Arduino Eigenschaft.
-----------
Im Moment versuche ich gerade die STD Lib von Wilhelm in das Arduino
Library Konzept zu überführen.
Einiges ist schon getestet, anderes macht noch Sorgen.
Es gibt störende Überschneidungen.
z.B. Macros im Arduino Framework welche mit Bezeichnern der STD Lib
kollidieren
Arduino Fanboy D. schrieb:> Im Moment versuche ich gerade die STD Lib von Wilhelm in das Arduino> Library Konzept zu überführen.> Einiges ist schon getestet, anderes macht noch Sorgen.> Es gibt störende Überschneidungen.> z.B. Macros im Arduino Framework welche mit Bezeichnern der STD Lib> kollidieren
Oh, da ist mittlerweile ja viel Zeit vergangen und auch das ganze Thema
hat sich sehr erfolgreich weiterentwickelt.
Vielleicht noch ein Hinweis:
die Meta-Code-Bibliothek (in meta.h) hat sich über die Zeit massiv
weiterentwickelt. Man kann schon zur Compilezeit wirklich coole Sachen
machen. Eine Alternative wäre halt Boost::Hana, wobei mir mein Ansatz,
mich nicht auf die Compileroptimierung zu verlassen und alle
Meta-Funktionen klassisch zu schreiben etwas besser gefällt ;-))
Die Modellierung der int. Periperie der Controller selbst kann
mittlerweile automatisch aus den XML-Dateien von MicroChip erzeugt
werden.
Durch den Erfolg der ganzen Sache ist es leider so, dass ich das ganze
nicht mehr als OSS veröffentlichen darf (das geht leider vielen
Entwicklern in diesem Kontext im Moment so).
Allerdings halte ich eine Zusammenführung der Arduino-Welt und den o.g.
Ansätzen für vergebliche Liebesmühe.
Wilhelm M. schrieb:> Durch den Erfolg der ganzen Sache ist es leider so, dass ich das ganze> nicht mehr als OSS veröffentlichen darf (das geht leider vielen> Entwicklern in diesem Kontext im Moment so).
Wer oder was hindert dich? Microchip? Dein Arbeitgeber?
Oliver
Arduino Fanboy D. schrieb:> Wenn mich meine Erinnerung nicht trügt war die Benutzung in etwa so:> setPin(led); // um eine LED einzuschalten.>> Das gefällt mir nicht so...> Ich hätte es gerne so:> led.setHigh();> oder> led = 1;> oder> led.set(HIGH)> oder> led(1)
Hier gilt die alte Regel: freie Funktionen sind als Realisierungsform
vorzuziehen (wenn möglich), da sie i.A. einen generischeren Code liefern
und auch oft Symmetrien liefern. Einstellige freie Funktionen gehören
zur öffentlichen Schnittstelle dieses Typs genauso wie öffentliche
Elementfunktionen. Außerdem tendieren Sie dazu, die Kohäsion eines Code
zu steigern. Und last but not least: in C++ gibt es ja auch Bestrebungen
zur einer uniform-call-syntax, wobei dann foo(a) und a.foo() syntaktisch
gleich wären.
Niklas G. schrieb:> Johann L. schrieb:>> Vielleicht so?>> Sicher dass das funktioniert? Da sind doch wieder die PORTx-Makros inkl.> Cast in einem contexpr-Kontext.
Immerhin wird es von g++ v8 geschluckt, ansonsten hätte ich kaum den
generierten Code posten können.
> Johann L. schrieb:>> obwohl es wegoptimiert werden könnte:>> Kann es ja nicht, weil man durch den nicht-inline Funktionsaufruf> erzwingt, dass die Instanz angelegt wird...
Natürlich kann es wegoptimiert werden! Schau dir mal aufmerksam den
generierten Code an:
1
_Z6call_Pv:
2
lds r24,_ZL5ports
3
lds r25,_ZL5ports+1
4
jmp _Z5use_P1P
5
6
.section .rodata
7
_ZL5ports:
8
.word 56
9
.word 50
Anstatt ".word 56" per LDS aus dem Speicher zu laden hätte der Wert auch
per LDI in die Register geladen werden können. Es wird nämlich ein
Objekt vom Typ P übergeben und nicht etwa eine Referenz darauf;
letzteres ist ein typischer Reflex bei manchen C++ Programmierern. Bei
so einem kleinen Objekt (nicht größer als die Breite eines Zeigers) ist
durchaus in Betracht zu ziehen, es per Value zu übergeben anstatt per
const Referenz / Zeiger. Referenz übergeben führt nur zu einer
unnötigen Dereferenzierung im Callee.
Hallo,
ich muss nochmal nachfragen. Weil mir das Problem mit dem Adresszugriff
und aktuellen Compilervorschriften noch nicht einleuchtet.
Was bisher funktionierte und aktuell nicht mehr ist:
Bsp.
Warum klappt das mit aktuellen Compiler nicht mehr? PIND usw sind in der
iomxyz.h definiert. Davon wird die Adressnummer geholt und in einem
Zeiger gespeichert. Alle Beteiltigten sind jederzeit bekannt.
abgeändert in funktioniert wieder:
Bsp.
Das ist praktisch nichts anderes, funktioniert. Nur das man jetzt die
Adresse nicht mehr aus der iomxyz.h automatisch ermitteln lassen kann
sondern quasi von Hand eintragen muss.
Wo liegt das Problem wirklich? Ich meine wozu habe ich denn die vielen
Headerdateien zur Verfügung wenn ich nicht darauf zugreifen darf?
Und da schmeckt dem Compiler dieser Ausdruck nicht:
> &(*(volatile uint8_t *)(0x102))
Dieser aber geht ohne Murren durch.
> &(*(volatile uint8_t *)((0x05) + 0x20))
Offensichtlich sind beide falsch.
Nur dass einer "besser" schmeckt.
Veit D. schrieb:> Wo liegt das Problem wirklich?
Das wurde gleich im zweiten Post beantwortet.
Niklas G. schrieb:> Die "PORTxx" sind Makros welche einen C-Style-Cast der Art "(volatile> uint8_t*) 0x1234" enthalten. Dieser wird vom C++-Compiler als> "reinterpret_cast<volatile uint8_t*> (0x1234)" verstanden (named cast),> daher die Ausgabe. Ein solches reinterpret_cast ist aber in> constexpr-Kontexten verboten.
mh schrieb:> Das wurde gleich im zweiten Post beantwortet.
Der Cast ist in beiden Ausdrücken identisch.
Ich interpretiere die Frage von Veit so:
Was ist die Ursache für die Ungleichbehandlung?
Arduino Fanboy D. schrieb:> mh schrieb:>> Das wurde gleich im zweiten Post beantwortet.> Der Cast ist in beiden Ausdrücken identisch.
Welche Ausdrücke? Die beiden Zeilen in seinem Post?
mh schrieb:> Welche Ausdrücke?
Diese, ich meine:
Arduino Fanboy D. schrieb:> Und da schmeckt dem Compiler dieser Ausdruck nicht:>> &(*(volatile uint8_t *)(0x102))>> Dieser aber geht ohne Murren durch.>> &(*(volatile uint8_t *)((0x05) + 0x20))
Die Addition hat da einen Seiteneffekt.
Einen, den eine Addition aus meiner begrenzten Sicht, nicht haben
dürfte.
Mit der Klammersetzung hat das nichts zu tun.
Die kann man verändern wie mal will, solange es konsistent bleibt.
Arduino Fanboy D. schrieb:> DPA schrieb:> In reinem C erlebt man keine solchen überraschungen...>> ? Was du meinen ?
C ist weniger mit features überladen, wird seltener erweitert, hat eine
simplere syntax, und wird deshalb vom mehr compilern vollständiger
unterstützt und die Standards (sofern einer ausgewählt) auch
eingehalten. Die Situation, dass es in einer neueren Kompilerversion mit
gleichem ausgewählten c standard plötzlich nichtmehr geht, gibt es dort
eigentlich nicht.
Arduino Fanboy D. schrieb:> mh schrieb:>> Welche Ausdrücke?>> Diese, ich meine:>> Arduino Fanboy D. schrieb:>> Und da schmeckt dem Compiler dieser Ausdruck nicht:>>> &(*(volatile uint8_t *)(0x102))>>>> Dieser aber geht ohne Murren durch.>>> &(*(volatile uint8_t *)((0x05) + 0x20))
Interessant, wie er sich auf die Ausdrücke beziehen kann, die erst nach
seinem Post von dir gepostet wurden.
Und der fehlende Fehler bei deinem zweiten Ausdrück ist vermutlich ein
Bug. Ersetzt man das + durch einen anderen Operator (außer -) wie z.B.
*, gibt es einen Fehler.
mh schrieb:> Interessant, wie er sich auf die Ausdrücke beziehen kann, die erst nach> seinem Post von dir gepostet wurden.
Um das zu verstehen wird man erst die Rekursion verstanden haben müssen.
(nee, es gibt eine Vorgeschichte, von der du wohl nichts wissen kannst)
Hallo,
ich hatte mich immer noch gewundert das der händische Eingriff, quasi
eine manualle Textersetzung, weil in der iomxyz.h nur defines stehen,
zum funktionieren animiert.
> Niklas G. schrieb:>> Die "PORTxx" sind Makros welche einen C-Style-Cast der Art "(volatile>> uint8_t*) 0x1234" enthalten. Dieser wird vom C++-Compiler als>> "reinterpret_cast<volatile uint8_t*> (0x1234)" verstanden (named cast),>> daher die Ausgabe. Ein solches reinterpret_cast ist aber in>> constexpr-Kontexten verboten.
Das muss man dann wohl aktzeptieren.
Jetzt mal gesponnen, um das cast Problem zu lösen müßte man theoretisch
die Header umschreiben ohne Makros?
In dem Sinne statt
1
# define PORTH _SFR_MEM8(0x102)
dann ungefähr so:
1
constuint16_tPORTH=0x0102;
Ich werde das nicht machen, weil man dann zum Rest der µC Welt
inkompatibel wird, aber rein zum Verständnis gefragt. Wäre das
theoretisch der Lösungsweg? Dann wäre mir zum verstehen schon geholfen.
Veit D. schrieb:> Hallo,>> ich hatte mich immer noch gewundert das der händische Eingriff, quasi> eine manualle Textersetzung, weil in der iomxyz.h nur defines stehen,> zum funktionieren animiert.>>> Niklas G. schrieb:>>> Die "PORTxx" sind Makros welche einen C-Style-Cast der Art "(volatile>>> uint8_t*) 0x1234" enthalten. Dieser wird vom C++-Compiler als>>> "reinterpret_cast<volatile uint8_t*> (0x1234)" verstanden (named cast),>>> daher die Ausgabe. Ein solches reinterpret_cast ist aber in>>> constexpr-Kontexten verboten.>> Das muss man dann wohl aktzeptieren.>> Jetzt mal gesponnen, um das cast Problem zu lösen müßte man theoretisch> die Header umschreiben ohne Makros?
.
> In dem Sinne statt>
1
>#definePORTH_SFR_MEM8(0x102)
2
>
>> dann ungefähr so:>
1
>constuint16_tPORTH=0x0102;
2
>
>> Ich werde das nicht machen, weil man dann zum Rest der µC Welt> inkompatibel wird, aber rein zum Verständnis gefragt. Wäre das> theoretisch der Lösungsweg? Dann wäre mir zum verstehen schon geholfen.
das Macro _SFR_MEM8(x) kann beliebiges machen, man muß es nur
umdefinieren.
Und man kann das AVR-Port-Bit-Problem mit C++-Typen und den
Original-Header-Files in den Griff bekommen, wobei ich zugebe, daß eine
solche Lösung ein böser Hack ist. Aber mir reicht sie.
>> Ich werde das nicht machen, weil man dann zum Rest der µC Welt> inkompatibel wird, aber rein zum Verständnis gefragt. Wäre das> theoretisch der Lösungsweg?
Nein.
Das Problem steckt ganz tief im Grundkonzept der Behandlung von SFIOR.
Dadurch, dass du die ganzen Sonderkonzepte für eine Anwendung ausser
Kraft setzt, machst du vieles andere kaputt, was richtig und nötig ist,
inbesondere die automagisch angenommene Volatilität von Zugriffen auf
SFIOR.
Das kann nur schief gehen.
Ich bin zufällig auf diesem Thread gestoßen.
Gerade vor ein Tagen habe ich meine eigene c++ Implementierung für das
Problem geschrieben.
Es ist hier zu finden: https://github.com/rigomate/avr8-templates
Im Endeffekt kann man so etwas machen:
1
usingPinName=Pin<PortB,1>;
2
3
voidfoo()
4
{
5
PinName::setpin();
6
}
wohl angemerkt ist es mit -O3 genauso Effizient als hätte man
Rigó M. schrieb:> Im Endeffekt kann man so etwas machen:using PinName = Pin<PortB, 1>;>
Statische Polymorphie und damit Compilezeit-Instanziierungen
(template-Instanziierungen) statt Laufzeit-Polymorphie und
Laufzeit-Instanziierungen für HW-Abstraktionen zu verwenden, ist der
richtige Weg. Gut, dass Du das erkannt hast. Und Du kannst diesen Weg
für alle interne Peripherie eines µC machen, wie etwa USART<>, etc.
Hier mal die Hintergründe etwas weiter ausgeführt:
Beitrag "Re: AVR GPIOR Bit Verwaltung C++"Rigó M. schrieb:> void foo()> {> PinName::setpin();> }
BTW: Naming is hard! Die Elementfunktion hätte ich einfach nur
`Pin::set()` genannt, denn `Pin::setPin()` ist redundant.