Arduino RAM sparen mit PROGMEM und strcpy_P()

Gast #6432834
Lesenswert?

Im Internet habe ich viel zum Thema "Arduino RAM sparen mit PROGMEM und 
strcpy_P()" gefunden. Daher hier nur ganz konkrete Detailfragen, die 
eben nicht klar beantwortet werden. Zunächst zwei Code-Beispiele in C:
1
void doSomething( char fall ) {
2
  char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
3

4
  // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
5
  ...
6
}

Nun die RAM-sparende Variante:
1
void doSomething( char fall ) {
2
  const static char Cmd[] PROGMEM = "Dies ist ein zu modifizierendes Textgerippe\n";
3
  char command[45];
4
  strcpy_P( command, Cmd );
5

6
  // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
7
  ...
8
}

Fragen dazu:

1) Stimmt es, dass beim 1. Programm der String vor main() zur Laufzeit 
ins Ram kopiert wird und dann beim Aufruf der Funktion doSomething() 
Platz auf dem Stack für command[] resierviert wird und zu Beginn der 
Funktion der String aus dem Ram an diesen Platz kopiert wird?

2) Stimmt es, dass beim 2. Programm beim Funktionsaufruf von 
doSomething() Platz auf dem Stack für command[] resierviert wird und 
dann nur mittels strcpy_P() direkt aus dem Flash in diesen Stack-Platz 
kopiert wird?

3) Wieviel länger braucht auf dem ATmega328 strcpy_P() als das Kopieren 
in 1)? 1 Taktzyklus pro Zeichen?
#6432850
Lesenswert?

Ardur schrieb:

> 1) Stimmt es, dass beim 1. Programm der String vor main() zur Laufzeit
> ins Ram kopiert wird und

Ja, denn das ist eine statische, lokale Variable.

> dann beim Aufruf der Funktion doSomething()
> Platz auf dem Stack für command[] resierviert wird und zu Beginn der
> Funktion der String aus dem Ram an diesen Platz kopiert wird?

Nein, denn die Variable ist statisch, sie ist schon dauerhaft angelegt, 
siehe oben.

> 2) Stimmt es, dass beim 2. Programm beim Funktionsaufruf von
> doSomething() Platz auf dem Stack für command[] resierviert wird und
> dann nur mittels strcpy_P() direkt aus dem Flash in diesen Stack-Platz
> kopiert wird?

Ja.

> 3) Wieviel länger braucht auf dem ATmega328 strcpy_P() als das Kopieren
> in 1)? 1 Taktzyklus pro Zeichen?

So in etwa. Aber 99,9% aller Programme stört das nicht, denn die CPU ist 
in den meisten Fällen mit anderen Sachen beschäftigt als zu 100% Strings 
zu kopieren.
(Firma: egal) #6433224
Lesenswert?

Frank M. schrieb:
> Doch:char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
>
> command ist nicht statisch im ersten Programm, deshalb wird dieses Array
> bei jedem Aufruf von doSomething() neu gefüllt.

Er sagt hier ja explizit das er "command" auch verändern können möchte 
und somit muss das im RAM stehen. Damit erstmal den ganzen Code zu 
durchsuchen das der Programmierer das hier eventuell falsch deklariert 
hat und er es eigentlich garnicht verändern will, soviel Esoterik ist 
nicht der Stiel von C/C++. Das geht öfters davon aus das der 
Programmierer weis was er will (und das auch richtig angeben kann:-).

folgender wäre zulässig:
1
char command[45] = "Dies ist ein zu modifizierendes Textgerippe\n";
2
strcpy(command, "Neuer Text");

Hier würde das ganze meckern:
1
const char command[] = "Dies ist ein zu modifizierendes Textgerippe\n";
2
strcpy(command, "Neuer Text");

Durch das const sagt der Programiere das die Variable in ihrem 
Gültigkeitsbereich nicht verändert werden darf und somit darf der 
Compiler/Linker das auch annehmen und entsprechende Optimierungen 
durchführen.
#6433264
Lesenswert?

Peter D. schrieb:
> Ardur schrieb:
>> Nun die RAM-sparende Variante:
>
> Spart das wirklich was?

Es spart die (unsichtbare) RAM-Kopie! Denn der String wird beim 
Funktionsaufruf NICHT direkt aus dem Flash initialisiert sondern aus der 
Kopie im RAM. Eben darum sind konstante Strings auf dem AVR Mist 
(RAM-Verschwendung), welche man besser mit den _P Funktionen sowie 
PSTR("") Makro umgeht. Das sollte DIR aber als altem AVR-Hasen aber 
bekannt sein.
#6433319
Lesenswert?

Hier der Test für jeden nachvollziehbar. Test2 und 3 sind praktisch 
identisch, wenn gleich test3 kompakter ist.
1
#include <avr/io.h>
2
#include <avr/pgmspace.h>
3

4
#define TEST 1
5

6
#if TEST==1
7
void test1(void) {
8
    int i=0;
9
    char command[45] = "Hallo RAM-Verbrauchstest!";
10

11
    while(command[i]) {
12
        PORTD = command[i];
13
    }
14
}
15
#endif
16

17
#if TEST==2
18
void test2(void) {
19
    int i=0;
20
    char command[45];
21
    const static char cmd_init[45] PROGMEM= "Hallo RAM-Verbrauchstest!";
22
    
23
    strcpy_P(command, cmd_init);
24
    while(command[i]) {
25
        PORTD = command[i];
26
    }
27
}
28
#endif
29

30
#if TEST==3
31
void test3(void) {
32
    int i=0;
33
    char command[45];
34
    
35
    strcpy_P(command, PSTR("Hallo RAM-Verbrauchstest!"));
36
    while(command[i]) {
37
        PORTD = command[i];
38
    }
39
}
40
#endif
41

42

43
int main(void) {
44
#if TEST==1
45
   test1();
46
#endif
47

48
#if TEST==2
49
   test2();
50
#endif
51

52
#if TEST==3
53
    test3();
54
#endif
55

56
}
1
Test Flash RAM
2
1      270  46
3
2      242   0
4
3      222   0
Gast #6434287
Lesenswert?

Dann ist also für meinen Fall, einen längeren String in einer Funktion 
verändern zu müssen, die von Falk B. (falk) vorgeschlagene 3. Variante 
die RAM- und Flash-sparendste:
1
void doSomething( char fall ) {
2
   char command[45];
3
   strcpy_P( command, PSTR("Dies ist ein zu modifizierendes Textgerippe\n") );
4

5
   // verändere jetzt etwas am String command, bevor er weiterverarbeitet wird
6
   ...
7
}

Ich bedanke mich ehrlich.
Gast #6434381
Lesenswert?

Wenn du den modifizerten Text irgendwo seriell ausgibts (z.B. auf einem 
Display) könntest du ihn auch in mehreren Stücke zerlegen. Beispiel:

Statt: "Die Temperatur ist # Grad Celsius", wo dann # durch die Zahl 
ersetzt wird, könntest zu auch das tun:

ausgeben_P(PSTR("Die Temperatur ist "));
ausgeben(zahl);
ausgeben_P(PSTR(" Grad Celsius"));

Das würde noch viel weniger RAM belegen.
Gast #6434890
Lesenswert?

Apollo M. schrieb:
> mich interessiert mal der Sinn von static hier, wenn die Variable doch
> sowieso "statisch" fix im Flash liegt

Wäre sie nicht static, könnte sie nicht im flash liegen, daher ist 
static nötig.

Falk B. schrieb:
> Das sollte DIR aber als altem AVR-Hasen aber bekannt sein.

Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte 
optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie 
unnötig ist, der könnte selber eine vernünftige Speicheraufteilung 
erzeugen, aber er ist offenkundig noch immer zu doof dazu.

DAS ist das eigentlich Erschreckende im Jahr 2020.

Die ganzen Jubelperser "der beste Compiler, optimiert super, besser ist 
man per Hand in Assembler nie" müsstest du ansprechen.

(ich musste gerade den Takt eines AVR von 1 auf 8 MHz hochsetzen, damit 
er eine simple Interruptroutine noch schafft, die nicht häufiger als 
alle 50us aufgerufen wird:)
1
uint16_t buffer[256];
2
uint8_t pos;
3
ISR(TIMER1_CAPT_vect)
4
{
5
  buffer[pos++]=ICR1;
6
  TCCR1B^=(1<<ICES1);
7
}

Der Compiler ist damit überfordert.
Gast #6434903
Lesenswert?

MaWin schrieb:
> Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte
> optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie
> unnötig ist, der könnte selber eine vernünftige Speicheraufteilung
> erzeugen, aber er ist offenkundig noch immer zu doof dazu.

Der arm-gcc kann diesbezüglich einiges besser, als der avr-gcc. 
Vermutlich hat es damit zu tun, dass AVR spezielle Befehle für den 
Zugriff auf den Flash Speicher brauchen, die gängigen ARM Controller 
aber nicht.
Gast #6434940
Lesenswert?

Stefan ⛄ F. schrieb:
> Statt: "Die Temperatur ist # Grad Celsius", wo dann # durch die Zahl
> ersetzt wird, könntest zu auch das tun:
>
> ausgeben_P(PSTR("Die Temperatur ist "));
> ausgeben(zahl);
> ausgeben_P(PSTR(" Grad Celsius"));
1
#include <Streaming.h>
2

3
void setup() 
4
{
5
  Serial.begin(9600);
6
  Serial << F("Start: ") << __FILE__ << endl;
7
}
8

9
void loop() 
10
{
11
  float grad {47.11};
12
  Serial << F("Die Temperatur ist ") << grad << F(" Grad Celsius") << endl;
13
}

F() Makro:
https://www.arduino.cc/reference/de/language/variables/utilities/progmem/
#6435343
Lesenswert?

MaWin schrieb:
> Das schlimme ist, dass es dem GCC Compiler nicht bekannt ist. Der könnte
> optimieren, er weiss ob in Speicher hineingeschrieben wird, welche Kopie
> unnötig ist, der könnte selber eine vernünftige Speicheraufteilung
> erzeugen, aber er ist offenkundig noch immer zu doof dazu.

Da das ja deiner Meinung nach so einfach zu implementieren ist: Na dann 
mal ran, MaWin. Muss ja ein Klacks für jemanden wie dich sein. ;)

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