Forum: Compiler & IDEs C++ Funktions Template für __flash


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.
von Harper B. (harper)


Lesenswert?

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<typename C>
2
bool Identical(uint8_t * buffer, C &data) {
3
  return 0 == memcmp(buffer, &data, sizeof(data));
4
}
5
template<typename C>
6
bool Identical(uint8_t * buffer, __flash C &data) {
7
  return 0 == memcmpf(buffer, &data, sizeof(data));
8
}
9
__flash int A;
10
int B;
11
bool foo() {
12
    uint8_t X[2] = { ... } ;
13
    if (Identical(&x, A)) {
14
        return 1;
15
    } else if (Identical(&x, B)) {
16
        return 2;
17
    }
18
    return 0;
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.

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Harper B. schrieb:
> sizeof(data)

Schau dir mal an was dies liefert und vergleiche das mal mit dem was du 
wirklich haben willst.

von Manuel H. (Firma: Universität Tartu) (xenos1984)


Lesenswert?

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

von Harper B. (harper)


Lesenswert?

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.

Beitrag #7837864 wurde vom Autor gelöscht.
von Harper B. (harper)


Lesenswert?

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?

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Harper B. schrieb:
> memcmpf

Klingt nach CodeVisionAVR, aber der kann kein C++?

__flash gibt's doch gar nicht in C++ beim AVR-GCC?

von Niklas G. (erlkoenig) Benutzerseite


Angehängte Dateien:

Lesenswert?

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(const char [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:
1
strlen (progmemArr2);
2
memcmp (AddrSpaces::ref_static_cast<const void> (ramArr2), AddrSpaces::ref_static_cast<const void> (progmemArr2), 3);

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.

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


Lesenswert?

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.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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++ und AVR auskennt.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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?

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von A. B. (Firma: ab) (bus_eng)


Lesenswert?

Vor längerer Zeit mal angeschaut:

https://github.com/KonstantinChizhov/Mcucpp/blob/dev/mcucpp/flashptr.h

Beispiel:
https://github.com/KonstantinChizhov/Mcucpp/blob/dev/mcucpp/impl/ftoa_engine.h
1
  static const uint32_t ftoaTable[] FLASH_STORAGE =
2
  {
3
    0xF0BDC21A,
4
    0x3DA137D5,  ... 
5
  };

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Andreas M. (amesser)


Angehängte Dateien:

Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von Harald (hasanus)


Lesenswert?

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:
1
template<typename T>
2
struct flash_storage
3
{
4
     T Var = {}; // PROGMEM ö.ä nötig
5
};
6
7
template<typename T>
8
bool identical(flash_storage<T> const& flash, T const & sram)
9
{
10
    // return memcmpf(&sram, &flash.Var, sizeof(T)))
11
    return true;
12
}
13
14
template<typename T>
15
bool identical(T const &sram, flash_storage<T> const & flash)
16
{
17
    return true;
18
    // return memcmpf(&sram, &flash.Var, sizeof(T)))
19
}
20
21
template<typename T>
22
bool identical(T l, T r)
23
{
24
    return l == r;
25
}
26
27
int sram = 12;
28
flash_storage<int> const flash;
29
30
int foo()
31
{
32
    int tmp = 123;
33
    if (identical(tmp, sram))
34
        return 1;
35
    else if (identical(tmp, flash))
36
        return 2;
37
    else if (identical(tmp, 456))
38
        return 3;
39
    else
40
        return 0;
41
}

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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 😉

: Bearbeitet durch User
von Harald (hasanus)


Lesenswert?

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.
1
#include <avr/pgmspace.h>
2
3
template<typename T>
4
static T pgm_read(T const*src )
5
{
6
    static_assert(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4);
7
    if constexpr (sizeof(T) == sizeof(uint8_t))
8
        return pgm_read_byte_near(src);
9
10
    else if constexpr (sizeof(T) == sizeof(uint16_t))
11
        return pgm_read_word_near(src);
12
13
    else if constexpr (sizeof(T) == sizeof(uint32_t))
14
        return pgm_read_dword_near(src);
15
}
16
17
template<typename T>
18
struct linear
19
{
20
    constexpr linear (T val): Val {val} {}
21
    constexpr linear (): Val {} {}
22
    T get() const { return Val; }
23
    void set(T r) { Val = r; }
24
25
    constexpr uint8_t size() { return 1; }
26
    T Val;
27
};
28
29
template<typename T, T... Vals>
30
struct progmem
31
{
32
    static_assert(sizeof...(Vals) == 1);
33
    progmem() = default;
34
    T get() const { return pgm_read<T>(vals); }
35
    T const & operator[] (uint8_t offs) const { return pgm_read<T>(vals + offs); }
36
    constexpr uint8_t size() { return sizeof...(Vals); }
37
38
    static constexpr T vals [] PROGMEM = { Vals... };
39
};
40
41
template<typename T, T... Vals>
42
struct progmem_array
43
{
44
    uint8_t constexpr size() { return sizeof...(Vals); }
45
    static constexpr T vals [] PROGMEM = { Vals... };
46
47
    T operator[] (uint8_t offs) const { return pgm_read<T>(vals + offs); }
48
};
49
50
template <typename T, template<class Func, T... Vals>class Model, T... Vals>
51
struct storage: Model<T, Vals...>
52
{
53
    using model = Model<T, Vals...>;
54
    storage(T val): Model<T, Vals...> {val} {}
55
    storage(): Model<T, Vals...>() {}
56
57
    bool operator==(T const & r) const { return model::get() == r; }
58
    bool operator==(storage const &r) const { return model::get() == r.model::get(); }
59
60
    template <typename T_R, template<class Func_R, T... Vals_R>class Model_R, T... RVals>
61
    bool operator==(storage<T_R, Model_R,  RVals...> const &r) const
62
    {
63
       return model::get() == r.get();
64
    }
65
};
66
67
storage<int, linear> int_lin { 12 };
68
storage<int, linear> other_lin;
69
storage<int, progmem, 123> int_pgm;
70
storage<uint32_t, progmem_array, 43, 39, 12, 65, 2, 21> magic_numbers;
71
static_assert(magic_numbers.size() == 6);
72
73
int main()
74
{
75
    if (int_lin == other_lin)
76
        return 1;
77
    if (int_lin == 2)
78
        return 2;
79
    if (int_lin == int_pgm)
80
        return 3;
81
}

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

von Harald (hasanus)


Lesenswert?

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.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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.

von A. B. (Firma: ab) (bus_eng)


Lesenswert?

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)); 
}

...

: Bearbeitet durch User
von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Da fehlt ein
1
#include <string.h>

Ist wohl beim Arduino standardmäßig dabei
1
reinterpret_cast' is not a constant expression

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

: Bearbeitet durch User
von Harald (hasanus)


Lesenswert?

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?

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

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

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
Noch kein Account? Hier anmelden.