C++ Funktions Template für __flash

#7837637
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.

(Firma: Universität Tartu) #7837733
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.

#7837863
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 von einem Moderator gelöscht.
Persönliche Seite #7837890
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

Persönliche Seite #7838035
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.

Angehängte Dateien:
Gast #7838090
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.

Persönliche Seite #7838104
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.

Persönliche Seite #7838215
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.

Persönliche Seite #7838271
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.

Persönliche Seite #7838475
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.

Persönliche Seite #7838689
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.

#7839193
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
}
#7839832
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
}
Persönliche Seite #7839836
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

#7839844
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.

Persönliche Seite #7839859
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.

(Firma: ab) #7839868
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)); }

...

Persönliche Seite #7839870
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

#7840412
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?

Persönliche Seite #7840419
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

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren