Forum: Mikrocontroller und Digitale Elektronik AVR GCC: __flash vs PROGMEM


von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Hallo,

im Atmel Studio 7 habe ich ein Problem mit "__flash". Im aktuellen 
Projekt arbeite ich damit statt mit dem "alten" PROGMEM. Ich lege ein 
Array einer Struktur an, die u.a. einen Pointer enthält, der auf einen 
vorher definierten String im Flash zeigt. Deklariert man diesen mit 
__flash geht das nicht, testweise mit PROGMEM (1. Zeile) schon:

const __flash char shortKeyTextStim[MENU_LCD_MENUTEXTLEN] = "Stim"; // 
hier der String im Flash

const __flash Menu_t menu_selFunc[] =
  {{...,...,...,...,shortKeyTextStim,...,...},... // <--- ERROR 
initializer element is not computable at load time


Ändert man die erste Deklaration auf

const char shortKeyTextStim[MENU_LCD_MENUTEXTLEN] PROGMEM = "Stim";

dann geht's.

Der Parameter ist dabei eigentlich ein pointer auf eine uint8_t 
RAM-Variable, der hier aber zweckentfremdet wird als Pointer ins flash 
(Auszug aus der typedef von Menu_t :
...
  uint8_t * pVar; // <--- normalerweise Pointer auf uint8 im RAM, aber 
hier eben ins Flash
...
)

Ursprünglich  hatte ich deswegen auch "shortKeyTextStim" in der 
array-initialisierung als (uint8_t *) gecastet, um die Warnung zu 
vermeiden, die man erwarten würde. Dann aber kommt "initializer element 
is not constant" als Fehler.

Was ist falsch bei __flash ?

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


Lesenswert?

Uwe G. schrieb:
> (Auszug aus der typedef von Menu_t :
> ...
>   uint8_t * pVar; // <--- normalerweise Pointer auf uint8 im RAM, aber
> hier eben ins Flash
> ...
1
const __flash uint8_t *pVar;

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Uwe G. schrieb:
> Ändert man die erste Deklaration auf
>
> const char shortKeyTextStim[MENU_LCD_MENUTEXTLEN] PROGMEM = "Stim";
>
> dann geht's.

Sollte aber mindestens eine Warnung geben, denn die richtige Deklaration 
von Menu_t.pVar ist in dem Fall:
1
const uint8_t *pVar;

Ohne das const Zeiger-Target von pVar könnte man schreibend auf *pVar 
zugreifen => Undefined Behaviour.  Das const bei menu_selFunc[] bedeutet 
nur dass dieses Objekte / Array read-only ist (also auch alle 
Komponenten), aber auf das Ziel von pVar hat das keinen Einfluss.

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


Lesenswert?

...und nicht uint8_t sondern char, weil shortKeyTextStim[] ein 
char-Array bzw. String ist.

von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Danke für die Antwort!

Die Definition der Struktur kann ich kaum ändern. pVar wird vielfach 
verwendet - meistens  als Pointer auf eine Variable (nicht Konstante), 
daher deklariere ich das als *pVar, das const wäre also hinderlich ;). 
Aber diesen 16-Bit-Platz benutze ich auch als simple Integer-Zahl, z.B. 
als typecast in der Form:
{...,...,...,...,(uint8_t *) MIDI_CHANNEL_2,...,...}

Das geht problemlos! Aber in meinem Problemfall mit __flash kommt die 1. 
Fehlermeldung und der Typecast gibt die genannte andere Fehlermeldung.

Programmtechnisch ist mehrfach sichergestellt, dass der "Pointer" pVar 
immer richtig interpretiert wird, das ist nicht das eigentliche Problem.

Sauberer wäre es natürlich,
1) für jeden möglichen Parameter ein eigenes Element in der Struktur zu 
definieren (Aber Speicherplatz, Tippaufwand)
2) eine union zu definieren (Aber: hier schwer als Konstante zu 
initialisieren).

Ist schon klar, dass "const __flash Menu_t menu_selFunc" lediglich sagt, 
dass das Array im Flash liegt, nicht das die Pointer-Ziele konstant sind 
- wie gesagt, dass sollen sie z.T. auch nicht sein ;)

von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Johann L. schrieb:
> ...und nicht uint8_t sondern char, weil shortKeyTextStim[] ein
> char-Array bzw. String ist.

doch, schon (uint8_t*), weil nach der Deklaration der Struktur hier ja 
ein Pointer auf uint8_t erwartet wird ;) Dass shortKeyTextStim ein 
Pointer auf ein char-Array ist, sieht der Compiler. Mit dem Typecast 
sage ich ihm: "Tu mal so, als ob dass ein (uint8_t*) wäre (wie du 
erwartest), ich weiß schon, dass der angegebene Ausdruck einen anderen 
Typ ergibt".

Bei der Verarbeitung des Pointers steht dann meinerseits ein weiterer 
typecast als
(const __flash char*) (soft_Key[i].pSelMenu->pVar)
drin, damit der Compiler weiß, dass es sich (in diesem Fall) bei pVar um 
einen Pointer auf einen string im Flash handelt (der an der Stelle dann 
auch benötigt wird)

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Mit einer Union sehe ich da überhaupt kein Problem, etwa:
1
typedef struct
2
{
3
    int i;
4
    union
5
    {
6
        const __flash char *pf;
7
        char *pc;
8
    } u;
9
} S;
10
11
extern char c[];
12
extern const __flash char f[];
13
14
const __flash S s1 = { 1, { .pc = c } };
15
const __flash S s2 = { 2, { .pf = f } };
16
17
void func (void)
18
{
19
    s1.u.pc[0] = s2.u.pf[0];
20
}

Du verwensest ja eh (GNU-)C99+, hast also auch designated Initializers.

von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Johann L. schrieb:
...
>     union
>     {
>         const __flash char *pf;
>         char *pc;
>     } u;
...
> const __flash S s1 = { 1, { .pc = c } };
> const __flash S s2 = { 2, { .pf = f } };

Ohne Fehler geht allerdings nur

const __flash S s1 = { 1, u = { .pc = c } }...

leider müsste ich dann hunderte von Initialisierungen ändern.

Und letztlich erklärt es beim ursprünglichen Problem nicht, warum bei 
der Deklaration die Stringkonstante:

const __flash char foo[] = "abc"

"foo" an der Stelle des "uint8_t * pVar" bei der Initialisierung der 
Struktur nicht akzeptiert wird, aber schon, wenn man das so deklariert:

const char foo[] PROGMEM = "abc"

obwohl beide Deklarationen ja äquivalent sein sollten.

bei letzter kommt zwar zurecht ein Warnung, da ein Pointer auf diesen 
String an einer Stelle verwendet, wo in der Struktur als "pVar" ein 
(uint8_t *) erwartet wird, das "const" des Ziels nicht mehr garantiert 
wird - aber es gibt keinen Fehler beim Compilieren.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Uwe G. schrieb:
> Johann L. schrieb:
> ...
>>     union
>>     {
>>         const __flash char *pf;
>>         char *pc;
>>     } u;
> ...
>> const __flash S s1 = { 1, { .pc = c } };
>> const __flash S s2 = { 2, { .pf = f } };
>
> Ohne Fehler geht allerdings nur
>
> const __flash S s1 = { 1, u = { .pc = c } }...

Nope.  Der von mir gezeigte Code compiliert problemlos mit v8, v7, v6, 
v5, v4.9 und v4.7. Wenn überhaupt müsste es ".u=" heißen und nicht "u=".

> leider müsste ich dann hunderte von Initialisierungen ändern.

Klingt nach "Wasch mich, aber mach mich nicht nass!"

> Und letztlich erklärt es beim ursprünglichen Problem nicht, warum bei
> der Deklaration die Stringkonstante:
>
> const __flash char foo[] = "abc"
>
> "foo" an der Stelle des "uint8_t * pVar" bei der Initialisierung der
> Struktur nicht akzeptiert wird,

Wegen der unterschiedlichen Address-Space Qualifier.

> obwohl beide Deklarationen ja äquivalent sein sollten.

Sind sie nicht.

: Bearbeitet durch User
von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Johann L. schrieb:
> Uwe G. schrieb:
>> Johann L. schrieb:
>> ...
>>>     union
>>>     {
>>>         const __flash char *pf;
>>>         char *pc;
>>>     } u;
>> ...
>>> const __flash S s1 = { 1, { .pc = c } };
>>> const __flash S s2 = { 2, { .pf = f } };
>>
>> Ohne Fehler geht allerdings nur
>>
>> const __flash S s1 = { 1, u = { .pc = c } }...
>
> Nope.  Der von mir gezeigte Code compiliert problemlos mit v8, v7, v6,
> v5, v4.9 und v4.7. Wenn überhaupt müsste es ".u=" heißen und nicht "u=".
Komisch, gestern hab ich mich scheinbar vertippt, da ging's nur mit 
".u=" (ja, der "." war natürlich im Quelltext)

>> leider müsste ich dann hunderte von Initialisierungen ändern.
> Klingt nach "Wasch mich, aber mach mich nicht nass!"
Frage hierzu: im älteren C-Standard konnte man nur das erste Element 
einer union initialisieren. Gilt das heute weiterhin, wenn ich nichts 
(".xyz = ...) angebe? Muss ich dann trotzdem ein "{}" um die Konstante 
(hier "&(eineVariable)" ) schreiben? Ich bekomme nach Einführung der 
union nämlich hunderte Warnings(keine Fehler!), dass die braces bei der 
Intialsierung fehlen.

> Wegen der unterschiedlichen Address-Space Qualifier.
Worauf die Fehlermeldung aber in keinster Weise hindeutet

>> obwohl beide Deklarationen ja äquivalent sein sollten.
> Sind sie nicht.
OK, dann sollte aber wenigstens ein typecast gehen, der aber eine 
genauso dumme Fehlermeldung ausspuckt

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Uwe G. schrieb:
> im älteren C-Standard konnte man nur das erste Element einer
> union initialisieren. Gilt das heute weiterhin, wenn ich nichts
> (".xyz = ...) angebe?

Für eine belastbare Antwort müsste ich jetzt ebenfalls im Standard 
kramen; aber üblicherweise sind Erweiterungen kompatibel mit den 
Vorgängerversionen.

> Muss ich dann trotzdem ein "{}" um die Konstante
> (hier "&(eineVariable)" ) schreiben? Ich bekomme nach Einführung der
> union nämlich hunderte Warnings(keine Fehler!), dass die braces bei
> der Intialsierung fehlen.

GNU-C kennt sowas wie Anonymous Unions, d.h. die Union braucht keinen 
Name und kann ohne solchen zugegriffen und initialisiert werden, also 
ohne das .u.  Voraussetzung ist, dass keine Namenskonflikte zwischen 
Union-Komponenten und höherrangigen Composite-Elementen bestehen:
1
typedef struct
2
{
3
    int i;
4
    union
5
    {
6
        const __flash char *pf;
7
        char *pc;
8
    }; // anonymous
9
} S;
10
11
extern char c[];
12
extern const __flash char f[];
13
14
const __flash S s1 = { 1, .pc = c };
15
const __flash S s2 = { 2, .pf = f };
16
17
void func (void)
18
{
19
    s1.pc[0] =  s2.pf[0];
20
}

Die Initialisierung von .pf könnte man auch schreiben als "{ f }" oder 
"{ .pf = f }", aber nur "f" geht nicht.  Und falls man f nicht sonstwo 
braucht, kann man es auch ohne f schreiben:
1
#define FSTR(X) (const __flash char[]) { X }
2
3
const __flash S s2 = { 2, .pf = FSTR ("Hallo") };

>> Wegen der unterschiedlichen Address-Space Qualifier.
> Worauf die Fehlermeldung aber in keinster Weise hindeutet

Ja stimmt. Die Meldung reflektiert eher Interna des Compilers.

>>> obwohl beide Deklarationen ja äquivalent sein sollten.
>> Sind sie nicht.
> OK, dann sollte aber wenigstens ein typecast gehen, der aber eine
> genauso dumme Fehlermeldung ausspuckt

Problem ist der erzeugte Code.  Ein Cast übersetzt in Zwischencode, und 
bei einem Initializer geht das eben nicht, auch wenn das im Ende keinen 
asm-Code erzeugt.  Wäre zwar auch anders denkbar — wenn z.B. die 
Darstellung nicht identisch ist wie etwa bei __memx, dann könnte das ein 
Backend per RELOC nach Lokazierung umsetzen — aber bislang hat das wohl 
niemand gebraucht oder war nicht wichtig genug, als dass jemand das 
Feature zum GCC hätte beitragen wollen.

Die wenigen Targets, die Address-Spaces implementieren, benutzen dies 
i.d.R für Memory Mapped I/O, haben also nicht die Anforderung, Objekte 
in und Zeiger auf ASes beschreiben zu müssen. Erschwerend kommt hinzu, 
dass ISO/IEC DTR 18037 da nicht zuende gedacht ist.  Per default liegt 
alles im AS Generic, also auch Literal "Hallo" in
1
const __flash char *pf = "Hallo";

Der einzige Weg, auf dieses Literal zuzugreifen, ist jedoch via pf, ergo 
ist der einzige sinnvolle AS für das Literal __flash.  Immerhin kann man 
ein Compound Literal draus machen so dass es funktioniert, aber toll ist 
das nicht, erschwert Portierung und blöderweise geht es nicht lokal:
1
void func (void)
2
{
3
    const __flash char *pf = FSTR ("Hallo");
4
}
5
error: compound literal qualified by address-space qualifier

von Uwe G. (Firma: Praxis) (uwe_grassme)


Lesenswert?

Die anonyme union war eine gute Idee, die ich umgesetzt habe. Der 
Tippaufwand hielt sich damit in Grenzen und jetzt sind die Typen klar 
definiert und müssen nicht gecastet werden.

Und Danke auch für die Ausführungen zum besseren Verständnis der 
Internas von __flash !

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.