Forum: Mikrocontroller und Digitale Elektronik Array über konstante C-String im Flash


von Curby23523 N. (Gast)


Lesenswert?

Hallo,

ich brauche einige konstante C-Strings mehrfach in meinem Code und 
möchte diese gerne in den Flash als Konstanten ablegen.

Z.B. unsigned char Konstanten lege ich auf diese Weise in den Flash (ich 
benutze einen Xmega):
1
const BYTE IMG_Settings[20*3+2] PROGMEM = {
2
  20,24,
3
  0,224,240,248,240,240,240,252,127,127,127,127,248,240,240,240,248,240,224,0,
4
  0,112,249,255,255,255,255,240,224,224,224,224,240,255,255,255,255,249,112,0,
5
  0,0,0,0,0,0,0,3,15,15,15,15,1,0,0,0,1,0,0,0
6
};

Zum Auslesen benutze ich die Funktion pgm_read_byte. Ich frage mich nun, 
wie ich das Ganze mit C-String bewerkstellige. Meine Vorstellung geht in 
diese Richtung, was aber natürlich nicht funktioniert:
1
const char* strings[] PROGMEM = {"Hallo", "Welt"};

Darüber hinaus ist auch die Frage, wie ich diese an andere Funktionen 
übergebe.

Für Hinweise wäre ich sehr dankbar!

von Arduinoquäler (Gast)


Lesenswert?

Ich hoffe dir hilft das Beispiel weiter:
1
void uart_putstring_progmem (PGM_P pString)
2
{
3
  uint8_t  ch;
4
5
  while ( (ch=pgm_read_byte(pString++)) != 0 )
6
    uart_putchar(ch);
7
8
} /* --- uart_putstring_progmem --- */

von Georg G. (df2au)


Lesenswert?

Lies mal hier:
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Flash_mit_flash_und_Embedded-C

mit "__flash" statt "PROGMEM" geht es viel einfacher.

von Stefan F. (Gast)


Lesenswert?

Ich glaube, bei dem konkreten Anwendungsfall ist __flash nicht 
einfacher, denn man kommt trotzdem nicht umhin, eine separate Variante 
zum Senden dieser Strings zu schreiben, die den String aus dem Flash 
Speicher liest, statt aus dem RAM.

von Georg G. (df2au)


Lesenswert?

Stefan U. schrieb:
> eine separate Variante
> zum Senden dieser Strings zu schreiben, die den String aus dem Flash
> Speicher liest

Genau diese Arbeit nimmt dir der Compiler ab, wenn er das Attribut 
__flash bei einer Konstanten findet.

von Curby23523 N. (Gast)


Lesenswert?

Das wäre eine Möglichkeit, aber warum kennt mein aktuelles AVR Studio 
"__flash" nicht?

von Stefan F. (Gast)


Lesenswert?

> Genau diese Arbeit nimmt dir der Compiler ab

Kannst mal ein Beispiel für eine Funktion zeigen, die sowohl normale 
Strings als auch Flash-Strings sendet?

von Georg G. (df2au)


Lesenswert?

Stefan U. schrieb:
> Beispiel für eine Funktion

Ist es zu viel verlangt, dass du dir einfach den zitierten Artikel 
ansiehst?

von Arduinoquäler (Gast)


Lesenswert?

Nils H. schrieb:
> aber warum kennt mein aktuelles AVR Studio "__flash" nicht?

Weil du vermutlich AVR Studio 4.19 oder älter verwendest.

Der Compiler der dabei ist "versteht" __flash noch nicht.

Irgendwann mit neuerem Compiler geht's dann ....

von Stefan E. (sternst)


Lesenswert?

Stefan U. schrieb:
> Kannst mal ein Beispiel für eine Funktion zeigen, die sowohl normale
> Strings als auch Flash-Strings sendet?
1
void SendStr ( const __memx char *str ) {
2
3
    while (*str)
4
        SendChr(*str++);
5
}

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Arduinoquäler schrieb:
> Der Compiler der dabei ist "versteht" __flash noch nicht.
>
> Irgendwann mit neuerem Compiler geht's dann ....

Wobei das eine GNU-Erweiterung ist, die es nicht in den Standard 
geschafft hat.  Daher -std=gnu99 o.ä.

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

>> Beispiel für eine Funktion

> Ist es zu viel verlangt, dass du dir einfach den zitierten Artikel
> ansiehst?

Ich habe mir das Beispiel angesehen. Dort sehe ich eine Funktion, die 
einen String ausschließlich aus dem Flash senden kann, aber keinen 
"normalen" String.

Ich kann das Programm auch nicht compilieren. Was mache ich falsch?

1
D:\Stefan\Elektronik\Test\src>make
2
avr-gcc -c -Wall -O1 -fshort-enums -mmcu=atmega328p -DF_CPU=8000000 -ffunction-sections -fdata-sections -std=c99   -o main.o main.c
3
main.c:5:22: error: expected '=', ',', ';', 'asm' or '__attribute__' before 'char'
4
 static const __flash char message2[]="Hello World!";
5
                      ^
6
main.c:7:22: warning: type defaults to 'int' in declaration of '__memx' [-Wimplicit-int]
7
 void sendChr ( const __memx char character )
8
                      ^
9
main.c:7:29: error: expected ';', ',' or ')' before 'char'
10
 void sendChr ( const __memx char character )
11
                             ^
12
main.c:13:22: warning: type defaults to 'int' in declaration of '__memx' [-Wimplicit-int]
13
 void sendStr ( const __memx char *str )
14
                      ^
15
main.c:13:29: error: expected ';', ',' or ')' before 'char'
16
 void sendStr ( const __memx char *str )
17
                             ^
18
main.c: In function 'main':
19
main.c:23:4: warning: implicit declaration of function 'sendStr' [-Wimplicit-function-declaration]
20
    sendStr(message1);
21
    ^
22
make: *** [main.o] Error 1
23
24
D:\Stefan\Elektronik\Test\src>avr-gcc -v
25
Using built-in specs.
26
Reading specs from d:/avr/avr-gcc/bin/../lib/gcc/avr/5.3.0/device-specs/specs-avr2
27
COLLECT_GCC=avr-gcc
28
COLLECT_LTO_WRAPPER=d:/avr/avr-gcc/bin/../libexec/gcc/avr/5.3.0/lto-wrapper.exe
29
Target: avr
30
Configured with: ../gcc-5.3.0/configure --target avr --enable-win32-registry=SysGCC-avr-5.3.0 --enable-languages=c,c++ --disable-nls --without-libiconv-prefix --prefix /q/gnu/auto/bu-2.25+gcc-5.3.0+gmp-5.1.3+mpfr-3.1.2+mpc-1.0.2-avr/ --host i686-pc-mingw32 --disable-shared --with-avrlibc=yes
31
Thread model: single
32
gcc version 5.3.0 (GCC)

> Irgendwann mit neuerem Compiler geht's dann ....

Ja, laut dem Artikel ab Version 4.7. Demnach hätte es bei mir klappen 
müssen, oder?

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Ich hatte den Standard c99 statt gnu99 konfiguriert. Nach der Korrektu 
bekomme ich eine andere Fehlermeldung:

1
D:\Stefan\Elektronik\Test\src>make clean code
2
rm -f main.o Test.hex Test.elf Test.lst Test.map Test_eeprom.hex
3
avr-gcc -c -Wall -O1 -fshort-enums -mmcu=atmega328p -DF_CPU=8000000 -ffunction-sections -fdata-sections -std=gnu99   -o main.o main.c
4
main.c:7:1: error: '__memx' specified for parameter 'character'
5
 void sendChr ( const __memx char character )
6
 ^
7
make: *** [main.o] Error 1

Wie schreibt man denn nun eine sendStr() Funktion, dass sie sowohl für 
RAM als auch für PROGMEM geeignet ist? Das Beispiel von Stefan Ernst 
konnte ich nicht anwenden, wie man sieht.

Ich würde es gerne verstehen.

von Stefan E. (sternst)


Lesenswert?

Stefan U. schrieb:
> Ich kann das Programm auch nicht compilieren. Was mache ich falsch?

void sendChr ( const __memx char character )
->
void sendChr ( char character )

von Stefan F. (Gast)


Angehängte Dateien:

Lesenswert?

Danke, das ergibt folgendes Assembler-Listing für die main() Funktion:
1
  cc:  80 e0         ldi  r24, 0x00  ; 0
2
  ce:  91 e0         ldi  r25, 0x01  ; 1
3
  d0:  6c 01         movw  r12, r24
4
  d2:  20 e8         ldi  r18, 0x80  ; 128
5
  d4:  e2 2e         mov  r14, r18
6
  d6:  8e 2d         mov  r24, r14
7
  d8:  b6 01         movw  r22, r12
8
  da:  0e 94 4b 00   call  0x96  ; 0x96 <sendStr>
9
  de:  8e 2d         mov  r24, r14
10
  e0:  b6 01         movw  r22, r12
11
  e2:  0e 94 4b 00   call  0x96  ; 0x96 <sendStr>

Ich finde den Aufruf der sendStr() Funktion seltsam. Bin ich blind, oder 
wird sie zweimal mit exakt dem gleichen Pointer (r24 und r22) 
aufgerufen? Woher weiss die Funktion dann, dass sie einmal auf RAM und 
danach auf PROGMEM zugreiden soll?

Und wenn ich mir die sendStr() Funktion genauer ansehe, vermisse ich 
auch hier die Unterscheidung zwischen den beiden Fällen:
1
00000096 <sendStr>:
2
  96:  cf 93         push  r28
3
  98:  df 93         push  r29
4
  9a:  a8 2f         mov  r26, r24
5
  9c:  cb 01         movw  r24, r22
6
  9e:  fc 01         movw  r30, r24
7
  a0:  24 91         lpm  r18, Z
8
  a2:  a7 fd         sbrc  r26, 7
9
  a4:  20 81         ld  r18, Z
10
  a6:  22 23         and  r18, r18
11
  a8:  59 f0         breq  .+22       ; 0xc0 <sendStr+0x2a>
12
  aa:  c6 ec         ldi  r28, 0xC6  ; 198
13
  ac:  d0 e0         ldi  r29, 0x00  ; 0
14
  ae:  01 96         adiw  r24, 0x01  ; 1
15
  b0:  a1 1d         adc  r26, r1
16
  b2:  28 83         st  Y, r18
17
  b4:  fc 01         movw  r30, r24
18
  b6:  24 91         lpm  r18, Z
19
  b8:  a7 fd         sbrc  r26, 7
20
  ba:  20 81         ld  r18, Z
21
  bc:  21 11         cpse  r18, r1
22
  be:  f7 cf         rjmp  .-18       ; 0xae <sendStr+0x18>
23
  c0:  df 91         pop  r29
24
  c2:  cf 91         pop  r28
25
  c4:  08 95         ret

Selbst wenn die den Optimizer abschalte (mit -O0) wird zwar geringfügig 
anderer Code erzeugt, aber ich sehe immer noch die selben Probleme.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Stefan U. schrieb:
> Ja, laut dem Artikel ab Version 4.7. Demnach hätte es bei mir klappen
> müssen, oder?

Nein.  Lies nochmals meinen Beitrag.

von Stefan E. (sternst)


Lesenswert?

Stefan U. schrieb:
> Ich finde den Aufruf der sendStr() Funktion seltsam. Bin ich blind, oder
> wird sie zweimal mit exakt dem gleichen Pointer (r24 und r22)
> aufgerufen?

Klar, weil du es selber so programmiert hast.
1
   sendStr(message1);
2
   sendStr(message1);

Stefan U. schrieb:
> Und wenn ich mir die sendStr() Funktion genauer ansehe, vermisse ich
> auch hier die Unterscheidung zwischen den beiden Fällen:

Die Unterscheidung ist hier:
1
  a2:  a7 fd         sbrc  r26, 7

von Stefan F. (Gast)


Lesenswert?

Ich blicke durch den Assembler Code von SendStr() nicht durch. Ich 
verstehe ihn so:

> lpm  r18, Z

Liest aus dem Programmspeicher

> sbrc  r26, 7
> ld  r18, Z

Überschribt das mit dem Wert aus dem RAM.
Was für ein quatsch, entweder soll er aus dem PROGMEM lesen oder aus dem 
RAM, aber doch nicht beides zusammen!

Jedenfalls scheint also r26 Bit 7 zu bestimmen, was eine Kopie von r24 
bit 7 ist. Demnach müsste das Hauptprogramm die Funktion einmal mit 
gesetztem Bit 7 und einmal mit gelöschtem Bit 7 aufrufen. Tut sie aber 
nicht.

Was übersehe ich da?

Nachtrag:
>> Klar, weil du es selber so programmiert hast.
>  sendStr(message1);
>  sendStr(message1);

Oh Mann, wie blöde. Vielen Dank, ich hab's jetzt geschnallt.
1
  d4:  60 e0         ldi  r22, 0x00  ; 0
2
  d6:  71 e0         ldi  r23, 0x01  ; 1
3
  d8:  80 e8         ldi  r24, 0x80  ; 128
4
  da:  0e 94 52 00   call  0xa4  ; 0xa4 <sendStr>
5
  de:  68 e6         ldi  r22, 0x68  ; 104
6
  e0:  70 e0         ldi  r23, 0x00  ; 0
7
  e2:  80 e0         ldi  r24, 0x00  ; 0
8
  e4:  0e 94 52 00   call  0xa4  ; 0xa4 <sendStr>

Sieht nun besser aus.

von Stefan E. (sternst)


Lesenswert?

Stefan U. schrieb:
> Was für ein quatsch, entweder soll er aus dem PROGMEM lesen oder aus dem
> RAM, aber doch nicht beides zusammen!

Dann mach doch mal einen Vorschlag, wie man das "entweder oder" 
effizienter lösen kann als mit der vorhandenen Befehlsfolge.

von Stefan F. (Gast)


Lesenswert?

Hmm, ich bräuchte dann wenigstens einen Sprung mehr und hätte dann 
tatsächlich keine einzigen Takt gewonnen.

Ok, der Compiler hat gewonnen. :-)

Schade, dass die avr-libc dieses Konstrukt nicht benutzt. Jetzt wo ich 
ich nachvollziehen kann gefällt es mir nämlich ganz gut.

von Curby23523 N. (Gast)


Lesenswert?

Nun zurück zum Thema, das __flash funltioniert soweit (natürlich 
verwende ich nicht eine Atmel Studio Version vom vorletzten 
Jahrhundert).

Ich schaffe es dennoch nicht, einen C-String mit getrennter Header und 
Source Datei so zu deklarieren und definieren. Gerne würde ich auch ein 
Struct mit mehreren Strings anlegen.

Header (das scheint zu funktionieren):
1
const __flash char *text;
2
3
const struct __flash{
4
  char * text1;
5
  char * text2;
6
} s_Test;

Source:
1
char *text = "Hallo Welt";
2
char s_Test.*text = "Hallo Welt";

Muss beim Source noch das const und flash auch hin? Ich habe alle 
möglichen Kombinationen probiert etc.

Es gibt den Error
1
expected '=', ',', ';', 'asm' or '__attribute__' before '.' token

von Stefan E. (sternst)


Lesenswert?

Nils H. schrieb:
> Header (das scheint zu funktionieren):

Es kompiliert vielleicht ohne Fehler, aber von "funktionieren" kann wohl 
kaum die Rede sein. Oder willst du tatsächlich nur die Pointer im Flash 
haben, die Texte selber aber im RAM?
Und ganz unabhängig von __flash: wieso sind im Header Definitionen und 
keine Deklarationen?

.h
1
#define FSTR(X) ((const __flash char[]){X})
2
3
typedef struct {
4
  const __flash char * text1;
5
  const __flash char * text2;
6
} s_Test_t;
7
8
extern const __flash char text[];
9
10
extern const __flash s_Test_t s_Test;

.c
1
const __flash char text[] = "Hallo Welt";
2
3
const __flash s_Test_t s_Test = {FSTR("Hallo Welt 1"),FSTR("Hallo Welt 2")};

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.