Array über konstante C-String im Flash

Gast #5004691
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!
Gast #5004711
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 --- */
Gast #5004728
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.
Gast #5004942
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?
Angehängte Dateien:
Gast #5004956
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.
Angehängte Dateien:
Gast #5004989
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.
Angehängte Dateien:
#5005005
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
Gast #5005019
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.
Gast #5005028
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.
Gast #5005624
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
#5005787
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")};

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