mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik [AVR] Unerwarteter Compilerfehler mit constexpr


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
Autor: Arduino Fanboy D. (ufuf)
Datum:
Angehängte Dateien:

Bewertung
-1 lesenswert
nicht lesenswert
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:
Fehler:10:82: error: reinterpret_cast from integer to pointer
   10 |  /**/ constexpr Register portList[] = {&PORTE,&PORTE,&PORTE,&PORTE,&PORTG,&PORTE,&PORTH,&PORTH,&PORTH,&PORTH,&PORTB,&PORTB,&PORTB,&PORTB,&PORTJ,&PORTJ,&PORTH,&PORTH,&PORTD,&PORTD,&PORTD,&PORTD,&PORTA,&PORTA,&PORTA,&PORTA,&PORTA,&PORTA,&PORTA,&PORTA,&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTC,&PORTD,&PORTG,&PORTG,&PORTG,&PORTL,&PORTL,&PORTL,&PORTL,&PORTL,&PORTL,&PORTL,&PORTL,&PORTB,&PORTB,&PORTB,&PORTB,&PORTF,&PORTF,&PORTF,&PORTF,&PORTF,&PORTF,&PORTF,&PORTF,&PORTK,&PORTK,&PORTK,&PORTK,&PORTK,&PORTK,&PORTK,&PORTK,};
      |                                                                                  ^
exit status 1
reinterpret_cast from integer to pointer


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?

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
4 lesenswert
nicht lesenswert
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 :-)

: Bearbeitet durch User
Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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;

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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.

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: Vincent H. (vinci)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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. :(

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: Niklas Gürtler (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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).

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr)
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
Der einzige für mich sichtbare Unterschied ist die Addition zweier 
Literale

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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...

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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:
 /*constexpr*/ uintptr_t portList[] = {(uintptr_t)&PORTH};
Geht ohne Meldung durch!

--------
 constexpr uintptr_t portList[] = {(uintptr_t)&PORTH};
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:
constexpr   int portList[] = {(int)&PORTB}; // geht durch
constexpr   int portList[] = {(int)&PORTH}; // Error
Auch wieder:
> error: reinterpret_cast from integer to pointer

: Bearbeitet durch User
Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
struct Register { std::uint16_t address; };

static constexpr Register R_PORTA { 0x0002 + __SFR_OFFSET };
static constexpr Register R_PORTB { 0x0005 + __SFR_OFFSET };
static constexpr Register R_PORTH { 0x0102 };

constexpr Register portList[] = { R_PORTA, R_PORTB };

void writePort (Register r, uint8_t value) {
  *reinterpret_cast<volatile uint8_t*> (r.address) = value;
}

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:
struct Register {
  std::uint16_t address;
  std::uint8_t read () const {
    return *reinterpret_cast<volatile std::uint16_t*> (address);
  }
  void write (std::uint8_t value) const {
    *reinterpret_cast<volatile std::uint16_t*> (address) = value;
  }
  bool readBit (std::uint8_t bit) const {
    return (read () >> bit) & 1;
  }
  void setBit (std::uint8_t bit, bool value) const {
    std::uint8_t x = read ();
    if (value) x |= (1 << bit); else x &= ~(1 << bit);
    write (x);
  }
};

struct Port {
  std::uint8_t iPort;
  constexpr std::uint16_t addrOffset () const {
    return (iPort >= 7) ? ((iPort-7)*3 + 0x100) : (iPort * 3);
  }
  constexpr Register pin () const { return { addrOffset () }; }
  constexpr Register ddr () const { return { addrOffset () + 1 }; }
  constexpr Register port () const { return { addrOffset () + 2 }; }
};

struct Pin {
  Port port;
  std::uint8_t iPin;
  void writeOut (bool value) const {
    port.port ().writeBit (iPin, value);
  }
  bool readIn () const {
    return port.readBit (iPin);
  }
  void dirOut () const {
    dir (true);
  }
  void dirIn () const {
    dir (false);
  }
  void dir (bool d) const {
    port.ddr ().writeBit (iPin, value);
  }
};

static constexpr PortA { 0 };
static constexpr PortB { 1 };
static constexpr PortC { 2 };
static constexpr PortD { 3 };
static constexpr PortE { 4 };
static constexpr PortF { 5 };
static constexpr PortG { 6 };
static constexpr PortH { 7 };
static constexpr PortI { 8 };
static constexpr PortJ { 9 };
static constexpr PortK { 10 };
static constexpr PortL { 11 };

constexpr Pin pinList [] = { { PortA, 3 }, { PortB, 2 }, { PortH, 7 } };

int main () {
  for (auto & p : pinList) {
    p.writeOut ();
    p.setOut (true);
  }
}

: Bearbeitet durch User
Autor: Arduino Fanboy D. (ufuf)
Datum:
Angehängte Dateien:

Bewertung
-1 lesenswert
nicht lesenswert
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!

: Bearbeitet durch User
Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Vielleicht so?
#include <avr/io.h>

struct P
{
    volatile uint8_t &p;
    uint8_t operator = (uint8_t val) const { return p = val; }
    operator uint8_t () const { return p; }
};

constexpr P ports[] = { PORTB, PORTD };

uint8_t func (bool z)
{
    ports[0] = ports[1] = 0;
    return ports[z];
}

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.

: Bearbeitet durch User
Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
1 lesenswert
nicht lesenswert
...blöderweise wird ports[] auch in folgendem Beispiel angelegt, obwohl 
es wegoptimiert werden könnte:
void use_P (P);

void call ()
{
    use_P (ports[0]);
}
_Z6call_Pv:
  lds r24,_ZL5ports
  lds r25,_ZL5ports+1
  jmp _Z5use_P1P

  .section  .rodata
_ZL5ports:
  .word  56
  .word  50

: Bearbeitet durch User
Autor: Arduino Fanboy D. (ufuf)
Datum:
Angehängte Dateien:

Bewertung
-1 lesenswert
nicht lesenswert
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.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
3 lesenswert
nicht lesenswert
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).

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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)
// Retriggerbares Monoflop / Flankenverzögerer
#include <CombiePin.h>
#include <CombieTimer.h>

Combie::Pin::TasterGND<8> taster; // Taster schaltet gegen GND
Combie::Pin::OutputPin<3> led;
Combie::Timer::FallingEdgeTimer fallingEdge(200);// ms Nachleuchtdauer

void setup()
{
  taster.initPullup();
  led.init();
}

void loop()
{

  // Datenfluss Schreibweise
  led = fallingEdge = taster;
  
/*
  // Functor Schreibweise
 led(fallingEdge(taster()));

  
  // Traditionelle Methodenaufruf Schreibweise
  led.set(fallingEdge.doTrigger(taster.pressed()));
*/
}



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.
?

Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Arduino Fanboy D. schrieb:
>> (wie std::integral_constant)
> Leider keine STD Lib
> Leider auch keine STL
> Nur die C-Lib

Ist zum Glück trivial:
template<class T, T v>
struct integral_constant {
    static constexpr T value = v;
    typedef T value_type;
    typedef integral_constant type; // using injected-class-name
    constexpr operator value_type() const noexcept { return value; }
    constexpr value_type operator()() const noexcept { return value; } 
};
https://en.cppreference.com/w/cpp/types/integral_constant

Arduino Fanboy D. schrieb:
> Ein Beispiel:
> (8 und 3 sind die Arduino Pin Nummern)

Das ginge so. Es haben dann halt alle Pins einen anderen Typ und sind 
nicht (ohne template) an Funktionen zu übergeben oder iterierbar.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
using led1 = Combie::Pin::OutputPin<3>;
using led2 = Combie::Pin::OutputPin<4>;

template<typename T> struct List {};

using listOfLeds = List<led1, led2>;

template<typename... Leds>
void foo(List<Leds...>) {
   (Leds::on(), ...);
}

void bla() {
   []<typename Leds>(List<Leds...>) {
          (Leds::toggle(), ...);
   }(List<led1, led2>{]);

}

void test()  {
    foo(listOfLeds{});
    bla();
}


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.

: Bearbeitet durch User
Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: Niklas G. (erlkoenig) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

: Bearbeitet durch User
Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Niklas G. schrieb:
> was wieder
> bei Mehrfachnutzung u.U. viel Code produzieren könnte.

Was natürlich für aktuelle PCs ein Riesenproblem ist...

Oliver

Autor: A. B. (Firma: uc++) (mitschreiberin)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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 !?

: Bearbeitet durch User
Autor: Arduino Fanboy D. (ufuf)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
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

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Oliver S. (oliverso)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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

Autor: Wilhelm M. (wimalopaan)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
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:
_Z6call_Pv:
  lds r24,_ZL5ports
  lds r25,_ZL5ports+1
  jmp _Z5use_P1P

  .section  .rodata
_ZL5ports:
  .word  56
  .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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.