Mit Hilfe von PROGMEM bzw. __flash kann ich eine const Variable im Flash
ablegen. In Funktionen kann ich jeweils prüft, ob in der Variable ein
vorher erzeugtes Pattern vorhanden ist. Für den Vergleich benötige ich
entweder memcmp oder memcmpf.
Ich möchte nun ein C++ Funktionstemplate erzeugen, um die Adresse, Größe
und den Speicherbereich (flash oder RAM) automatisch zu erkennen. Das
sollte in etwa so aussehen:
1
template<typenameC>
2
boolIdentical(uint8_t*buffer,C&data){
3
return0==memcmp(buffer,&data,sizeof(data));
4
}
5
template<typenameC>
6
boolIdentical(uint8_t*buffer,__flashC&data){
7
return0==memcmpf(buffer,&data,sizeof(data));
8
}
9
__flashintA;
10
intB;
11
boolfoo(){
12
uint8_tX[2]={...};
13
if(Identical(&x,A)){
14
return1;
15
}elseif(Identical(&x,B)){
16
return2;
17
}
18
return0;
19
}
Die Realität ist komplexer, aber die Idee sollte damit beschrieben sein.
Leider funktioniert die Überladung nicht. Der Compiler erkennt keinen
unterschiedlichen Type wenn das __flash Attribut angegeben ist oder
nicht.
Gibt es einen Weg den Compiler hier zu überzeugen, dass das __flash ein
Typbestandteil ist? Die Alternative, hier verschiedenen Funktionsnamen
zu verwenden ist mir bewusst, aber unschön.
__flash deklariert keinen neuen Typ, daher kann man dadurch kein anderes
Template aufrufen.
In der Arduino-Bibliothek behilft man sich mit einem Trick, indem man
eine neue Klasse (und damit einen neuen Typ) namens __FlashStringHelper
deklariert und dann ein Makro _F definiert, das einen gewöhnlichen
C-String (Zeiger auf Zeichenkette) im Flash in einen Zeiger auf diese
neue Klasse "umformt":
https://github.com/arduino/ArduinoCore-avr/blob/c8c514c9a19602542bc32c7033f48fecbbda4401/cores/arduino/WString.h#L37
Der Zeiger _F("bla") ist dann ein Zeiger auf einen anderen Typ.
Manuel H. schrieb:> __flash deklariert keinen neuen Typ, daher kann man dadurch kein anderes> Template aufrufen.>
Tja das ist schade, aber wohl nicht zu ändern.
Danke für den Hinweis eine eine mögliche Hilfsklasse wie in WString.h.
Dann würde ich aber bei den Variablen im Flash einen entsprechende cast
oder eine temporäre Instanz der Hilfsklasse benötigen.
Das ist mehr Aufwand, als zwei Funktionen, eine für RAM Und eine für
Flash zu benutzen.
Irgend W. schrieb:>> sizeof(data)>> Schau dir mal an was dies liefert und vergleiche das mal mit dem was du> wirklich haben willst.
sizeof auf einer Referenz liefert die Größe der übergebenen,
referenzierten Variable. Womit möchtest Du das vergleichen?
Harper B. schrieb:> Mit Hilfe von PROGMEM bzw. __flash
Über welchen Compiler reden wir überhaupt?
In GCC wird __flash unterstützt als Erweiterung in GNU-C:
>> As an extension, GNU C supports named address spaces as>> defined in the N1275 draft of ISO/IEC DTR 18037.https://gcc.gnu.org/onlinedocs/gcc/Named-Address-Spaces.html
Hab mich mal daran versucht so eine Art statischen Wrapper für die
verschiedenen AVR-Adress-Spaces zu basteln. Sodass man Funktionen anhand
des Pointer-Typs überladen kann. Es ist alles statisch, also keine
"generischen Pointer" die zur Laufzeit weitergegeben werden - es wird
nur der Typ mittels templates annotiert.
Ist leider doch etwas komplizierter geworden als erhofft. Man kann dann
so schreiben:
1
SPACES_LINEAR(char[3],ramArr2,1,2,3);
2
SPACES_LINEAR(constchar[3],ramArr3,1,2,3);
3
SPACES_PROGMEM_NEAR(char[3],progmemArr2,1,2,3);
4
SPACES_PROGMEM_FAR(char[4],progmemArr3,1,2,3);
5
6
7
SPACES_PROGMEM_NEAR(int,progmemVar,42);
8
SPACES_PROGMEM_FAR(int,progmemVar2,33);
Also Variablen in die verschiedenen Speicherbereiche legen. Man kann
dann "einfach so" zugreifen:
1
ramArr2.get(2);
2
ramArr3.get(2);
3
progmemArr2.get(2);
4
progmemArr3.get(2);
5
6
progmemVar.get();
7
progmemVar2.get();
Wobei automatisch die richtige Zugriffsmethode gewählt wird. Dann kann
man beliebige Funktionen überladen, das habe ich exemplarisch für strlen
und memcmp gemacht. Die kann man dann so aufrufen:
Für das memcmp muss man noch das etwas hässliche ref_static_cast
reinschieben um aus dem char-Pointer einen void-Pointer zu machen; bei
"nackten" Pointern geht das natürlich automatisch, aber durch das
Wrappen im template muss man es manuell machen. Aber man muss eben nicht
angeben, welches memcmp es konkret ist, das wird automatisch
entschieden.
"Normal" angelegte Variablen (im RAM) kann man via AddrSpaces::linear()
ebenfalls annotieren/wrappen, sodass man dann ebenso überladen kann.
Es werden Daten im RAM, PROGMEM, PROGMEM_FAR unterstützt. Man kann aber
prinzipiell beliebig Bereiche hinzufügen.
Wenn man dann den Zugriff auf den Flash Speicher "einfach so"
abstrahiert hat, wird es damit auch "ganz einfach", ein schlechtes
Programm zu schreiben (dass den Flash wie RAM zu behandeln versucht)
eben weil man die Komplexität dahinter zu sehr verborgen hat. Dazu
kommt, dass die vielen nützlichen Fremd-Bibliotheken dein Konstrukt
allesamt nicht nutzen können.
Ich würde es lassen, ist den Aufwand nicht Wert.
Sherlock 🕵🏽♂️ schrieb:> dass den Flash wie RAM zu behandeln versucht
Versehentlich auf den Flash schreiben kann man damit nicht. Und für
alles andere ist es egal, der Flash ist ja schnell.
Sherlock 🕵🏽♂️ schrieb:> eben weil man die Komplexität dahinter zu sehr verborgen hat
Welche Komplexität? Da ist ja nur die LPM-Instruktion. Warum sollte man
die manuell bespielen?
Sherlock 🕵🏽♂️ schrieb:> Dazu> kommt, dass die vielen nützlichen Fremd-Bibliotheken dein Konstrukt> allesamt nicht nutzen können.
Zum Anbinden von Fremdbibliotheken kann man immer noch einfach direkt
zugreifen, oder einen Wrapper für die Bibliothek schnallen für
unterschiedliche Varianten der Funktionen; genau das habe ich ja für
strlen vs strlen_P vs strlen_PF gemacht. Das ist ja sowieso genau das,
was ursprünglich gefragt war.
Niklas G. schrieb:> Hab mich mal daran versucht
Am naheliegendsten wäre ja, __flash etc. in avr-g++ einzubauen.
Müsste halt jemand machen, der sich in C++ undAVR auskennt.
Johann L. schrieb:> Am naheliegendsten wäre ja, __flash etc. in avr-g++ einzubauen.
Ja, allerdings ist es dann ja immer noch kein eigener Typ. Der __flash
Qualifier müsste genau wie "const" und "volatile" den Typ ändern, aber
das würde das Typsystem derart durcheinander bringen dass man praktisch
mit einer neuen Sprache da steht.
Niklas G. schrieb:> Johann L. schrieb:>> Am naheliegendsten wäre ja, __flash etc. in avr-g++ einzubauen.>> Ja, allerdings ist es dann ja immer noch kein eigener Typ. Der __flash> Qualifier müsste genau wie "const" und "volatile" den Typ ändern, aber> das würde das Typsystem derart durcheinander bringen dass man praktisch> mit einer neuen Sprache da steht.
Käse, in clang/llvm geht's ja auch, inclusive Mangling etc.
Johann L. schrieb:> Käse, in clang/llvm geht's ja auch.
Kann man da auf Basis von __flash überladen? Wie funktioniert dann zB
std::remove_cv? Muss man dann für Typfunktionen wie std::remove_pointer
neue Overloads hinzufügen und auch dementsprechend sehr viel bestehenden
Code anpassen?
Niklas G. schrieb:> Johann L. schrieb:>> in clang/llvm geht's ja auch.>> Kann man da auf Basis von __flash überladen? Wie funktioniert dann zB> std::remove_cv? Muss man dann für Typfunktionen wie std::remove_pointer> neue Overloads hinzufügen und auch dementsprechend sehr viel bestehenden> Code anpassen?
Mit clang kenn ich mich nicht aus. Builds sind aber im Netz verfügbar.
Und __attribute__((address_space(x))) ist im Gegensatz zu GCC (wo
Attribute nie Qualifier sind) als Qualifier implementiert, steht also
auf der selben Ebene wie const und volatile.
Johann L. schrieb:> Und attribute((address_space(x))) ist im Gegensatz zu GCC (wo Attribute> nie Qualifier sind) als Qualifier implementiert, steht also auf der> selben Ebene wie const und volatile.
Klingt zwar gut aber ich vermute dass dann sehr viel existierender Code
wie so ziemlich die ganze C++ Standardbibliothek nicht mehr
funktioniert, außer man setzt es nur sehr punktuell ein, das ist dann
aber genau so gut wie die GCC Lösung.
A. B. schrieb:> Vor längerer Zeit mal angeschaut:
Die Pointer-Arithmetik ist da vollständiger umgesetzt als bei mir aber
das mit den Overloads und automatischem Aufrufen der richtigen Funktion
ist da gar nicht umgesetzt.
Ich würde versuchen mich bei solchen Sachen an der C++ Standard Lib
bezüglich der Schnittstelle zu orientieren. Dann kann man später auch
viele Standardsachen mit nutzen, siehe Anhang.
Andreas M. schrieb:> Ich würde versuchen mich bei solchen Sachen an der C++ Standard Lib> bezüglich der Schnittstelle zu orientieren.
Das könnte man bei mir gut in der "Ref" Klasse unterbringen. Der mangelt
es momentan noch stark an den "++" operatoren etc.
Harper B. schrieb:> Gibt es einen Weg den Compiler hier zu überzeugen, dass das __flash ein> Typbestandteil ist?
Ja: alle flash - relevanten Operationen in einen eigenen Typ
verschieben. Dein Beispiel mit globalen Funktionen könnte z.B so
aussehen:
Harald schrieb:> T Var = {}; // PROGMEM ö.ä nötig
PROGMEM an einer Member-Variable?!
PS: Meine Version ist übrigens bisher die einzige welche auch mit Daten
im "far" Bereich klarkommt, also jenseits der 64KiB Grenze 😉
Niklas G. schrieb:> PROGMEM an einer Member-Variable?!
Du hast recht, das klappt nicht als nichtstatisches Member.
Wohl aber als statisches. Zudem kann man variadic template parameters
direkt zur Initialisierung verwenden (Zeilen 38 und 45). Das Erspart
hantieren mit memcmp, Makros etc.
Die zwei ersten template - Parameter sind Datentyp und Speichermodell
(linear progmem progmem_array). Für progmem_array folgen die im
PROGMEM abzulegenden Werte als Liste.
Harald schrieb:> Zudem kann man variadic template parameters direkt zur Initialisierung> verwenden (Zeilen 38 und 45)
Finde ich nicht so elegant, gerade auch bei komplexeren Datenstrukturen.
Allerdings funktioniert es ja weil man wohl nicht mehrfach die gleiche
Datenstruktur im Flash anlegen möchte. Bei mir kann man es ganz
klassisch per Konstruktor übergeben.
Was ist der Unterschied zwischen progmem und progmem_array?
Harald schrieb:> storage<int, progmem, 123> int_pgm;>> storage<uint32_t, progmem_array, 43, 39, 12, 65, 2, 21> magic_numbers;
Diese Variablen sind ja leer, verbrauchen aber vermutlich trotzdem RAM
(jeweils 1 Byte).
Harald schrieb:> Das Erspart hantieren mit memcmp,
Das memcmp war sicherlich nur ein Beispiel, es gibt ja noch einige mehr
Funktionen die für RAM/Flash unterschiedlich sind. Die effizient
überladen zu können war das Ziel
Niklas G. schrieb:> Was ist der Unterschied zwischen progmem und progmem_array?
progmem speichert einen einzigen Wert im PROGMEM, der kann dann mit
get() geholt werden.
progmem_array speichert die Parameter 3 ff im PROGMEM. Die könnten mit
dem [] operator geholt werden.
Niklas G. schrieb:> Diese Variablen sind ja leer, verbrauchen aber vermutlich trotzdem RAM> (jeweils 1 Byte).
Ich gehe von einem byte Verbrauch aus. Das ist der Preis für die
homogene Syntax. Tatsächlich braucht das Beispielprogramm nur 4 Bytes
für die linear storages, also für zwei ints. Ich vermute GCC kann das
bisschen code noch optimieren, aber bei mehr code wird er dann ein Byte
pro progmem belegen.
Auf die Schnelle bekomme ich deinen Code nicht kompiliert. Gibts da
einen Trick? Ich habe keine Arduino, sondern nur avr-gcc 13.3.
Harald schrieb:> progmem_array speichert die Parameter 3 ff im PROGMEM. Die könnten mit> dem [] operator geholt werden.
Du könntest auch einen Array-Typ als Parameter an progmem übergeben
lassen und dann automatisch operator [] aktivieren oder eben nicht. Dann
muss der User das nicht explizit unterscheiden.
Harald schrieb:> die Schnelle bekomme ich deinen Code nicht kompiliert. Gibts da einen> Trick?
Hmm, wie lautet die Fehlermeldung? Bis auf loop/setup ist da nichts
Arduino spezifisches. Hatte nur Arduino genommen weil es schon
installiert ist.
nur schnelles copy paste: ohne Gewähr
https://godbolt.org/z/z8zch1ssn
<source>: In static member function 'static size_t
AddrSpaces::Linear::strlen(pointer)':
<source>:108:92: error: '::strlen' has not been declared
108 | static inline __attribute__((always_inline)) size_t
strlen (pointer ptr) { return ::strlen (static_cast<const char*> (ptr));
}
...
Ahh, der alte GCC vom Arduino ist da nachlässiger. Müsste man umbauen
können damit es auch mit aktuellem GCC geht, hmm.
Das ProgmemFar Zeug geht natürlich nur bei Controllern mit mehr als
64KiB Flash
Niklas G. schrieb:> reinterpret_cast' is not a constant expression
gemeint ist der hier:
static inline __attribute__((always_inline)) constexpr pointer getAddr
() { return const_cast<pointer> (reinterpret_cast<const void*>
(addressof (Obj))); }
Der cast ist überflüssig und zugleich einer zuviel, da addressof eh ein
void* zurückgibt.
Niklas G. schrieb:> Ahh, der alte GCC vom Arduino ist da nachlässiger
Wie alt ist er denn? reinterpret_cast ist nicht constexpr. Vielleicht
war es in alten C++ - Versionen?
Harald schrieb:> gemeint ist der hier
Ich weiß...
Harald schrieb:> Der cast ist überflüssig und zugleich einer zuviel, da addressof eh ein> void*
Nein, es gibt T* oder const T* zurück. Erst const_cast und dann
reinterpret_cast wäre lästig wegen der Typangabe, daher einfach erst
nach "const void*".
Harald schrieb:> Wie alt ist er denn
Von 2017 glaub ich.
Harald schrieb:> reinterpret_cast ist nicht constexpr. Vielleicht war es in alten C++ -> Versionen?
Richtig, nein war es nie, die alten GCCs haben es nur nicht angemeckert.
Wäre wohl doch besser den "pointer" Typ zum template zu machen und erst
beim Zugriff in Progmem zu casten, und für Linear halt nie casten. Mal
schauen wann ich dazu komme das einzubauen