string an Funktion übergeben - Problem Speicherverbrauch RAM

OP #3161512
Lesenswert?

Hallo Leute,

derzeit habe ich folgendes Problem an meinem Projekt:

Hier ein kurzer Überblick:
Ich verwende den Arduino MINI ATMega328 und will einer Funktion im 
Hauptprogramm einen String übergeben damit dann im Unterprogramm etwas 
passiert.
Genau genommen soll dieser String dann über eine SoftwareSerial 
Schnittstelle ( in meinem Fall heißt sie Scada ) an ein anderes 
Computerprogramm (Scada Programm ) geschickt werden.

Da mein System schon nahezu fertig programmmiert ist, und das ganze 
Projekt fast 3000 Codezeilen verschlang, bin ich mit dem verfügbaren RAM 
derzeit nur mehr auf 358byte (freeMemory() - liefert mir das zurück )

Ich habe den Code so umgesetzt.

1
void loop()
2
{
3
    //do some other work here
4
    sendToScada("this text should be send to a scada system");
5
}
6

7

8

9
void sendToScada(char* sMessageToSend)
10
{
11
   Scada.listen();
12
   Scada.print("V"); // Messageheader
13
   for(int i=0;i<strlen(sMessageToSend);i++)
14
       Scada.print(sMessageToSend[i]);
15
   Scada.print("\n");
16
   XBee.listen(); //set to other serial active
17
}

Was mache ich da falsch? Mit dieser Funktion scheitre ich, denn nach 
einpaar aufrufen startet mein Arduino neu, d.h ich vermute dass der 
Speicher rasant gegen 0 geht und dann einen Neustart verursacht.


lg
Dieter
(Firma: guloshop.de) #3161608
Lesenswert?

Hallo,
mit deinen String-Operationen wird kein zusätzlicher Speicher 
"verbraucht". Es handelt sich ja nur um die drei Konstanten, und die 
brauchen nur so viele Bytes wie sie lang sind (zuzüglich jeweils ein 
Byte für den Terminator).

Bleiben als mögliche Speicherfresser "nur" noch deine Prozeduren 
Scada.listen(), Scada.print() und XBee.listen().

Kannst du davon den Quellcode posten?
OP #3161710
Lesenswert?

Hallo Markus,


so ich habe nun von der SoftwareSerial Lib die Quellcodes durchforstet.

Ich habe nun folgende Funktion gefunden, welche durch Scada.print 
aufgerufen wird.
1
size_t SoftwareSerial::write(uint8_t b)
2
{
3
  if (_tx_delay == 0) {
4
    setWriteError();
5
    return 0;
6
  }
7

8
  uint8_t oldSREG = SREG;
9
  cli();  // turn off interrupts for a clean txmit
10

11
  // Write the start bit
12
  tx_pin_write(_inverse_logic ? HIGH : LOW);
13
  tunedDelay(_tx_delay + XMIT_START_ADJUSTMENT);
14

15
  // Write each of the 8 bits
16
  if (_inverse_logic)
17
  {
18
    for (byte mask = 0x01; mask; mask <<= 1)
19
    {
20
      if (b & mask) // choose bit
21
        tx_pin_write(LOW); // send 1
22
      else
23
        tx_pin_write(HIGH); // send 0
24
    
25
      tunedDelay(_tx_delay);
26
    }
27

28
    tx_pin_write(LOW); // restore pin to natural state
29
  }
30
  else
31
  {
32
    for (byte mask = 0x01; mask; mask <<= 1)
33
    {
34
      if (b & mask) // choose bit
35
        tx_pin_write(HIGH); // send 1
36
      else
37
        tx_pin_write(LOW); // send 0
38
    
39
      tunedDelay(_tx_delay);
40
    }
41

42
    tx_pin_write(HIGH); // restore pin to natural state
43
  }
44

45
  SREG = oldSREG; // turn interrupts back on
46
  tunedDelay(_tx_delay);
47
  
48
  return 1;
49
}
Gast #3161760
Lesenswert?

> sendToScada("this text should be send to a scada system\0");

Wenn Du Speicherprobleme hast, solltest Du solche Spielereien lassen.

Es geht, es hat nämlich zur Folge, dass zwei Null-Zeichen am Ende des
Strings stehen.

strlen () macht beim ersten Feierabend, weshalb kein Fehler auftritt.

Wenn Dein Empfänger ein Windows-System ist, so pass mit dem "\n" auf.
Manche Programme stehen explizit auf Wagenrücklauf + Neue Zeile, 
andernfalls gibt's Kuddel-Muddel.
#3161775
Lesenswert?

amateur schrieb:
> @Uwe
>
> "V" und "\n" sind echte Strings.
Das ist mir schon klar (abgesehen davon dass es in C weder Strings noch 
Arrays gibt, sondern nur syntax sugar für Pointer-Arithmetik, aber das 
wird jetzt philosophisch...).

Meine Bemerkung zielt darauf, dass bei
Scada.print(sMessageToSend[i]);
mit einem char und bei
Scada.print("\n");
mit einem string aufgerufen wird.
#3161779
Lesenswert?

Ich hab das Programm mal eben durch den GCC geschoben (geht nur, wenn 
man sämtliche Warnungen deaktiviert), in zwei Varianten:

a) Scada.print(char c);
Effekt: der "this text..." String wird korrekt ausgegeben, statt dem "V" 
und "\n" kommt jeweils ein Zeichen Müll. Liegt daran, dass die Funktion 
das untere Byte des Zeigers auf den "V" bzw. "\n"-Sting ausgibt.

b) Scada.print(char * str);
Effekt: das "V" wird korrekt ausgegeben, danach stürzt das Programm ab. 
Ist auch einleuchtend: die Funktion interpretiert das übergebene char 
als einen Zeiger, folgt ihm ins Nirvana und verursacht eine 
Schutzverletzung.
(Firma: guloshop.de) #3161784
Lesenswert?

Uwe ... schrieb:
> Meine Bemerkung zielt darauf, dass bei
> Scada.print(sMessageToSend[i]);
> mit einem char und bei
> Scada.print("\n");
> mit einem string aufgerufen wird.

Ja, wirkt wirklich komisch. Aber vielleicht gibt es zwei 
print()-Prozeduren, eine für char und eine für const char* ? Gehts um 
C++? Genaueres würde man wahrscheinlich nur in der Doku oder im 
Quellcode zu diesem print() finden...
Gast #3161811
Lesenswert?

Werf die dynamische Speicherverwaltung via Malloc raus und verwende 
statische Ramverwaltung.

Dann kann die der Linker auch eine Ramausnutzung anzeigen die verwendbar 
ist.
Aufpassen musst du dann nur noch mit dem Stack.

Meiner Meinung nach gehört diese dynamische Speicherverwaltung für µC 
verboten. En µC hat eben nicht unbegrenztes RAM wie ein PC sondern nur 
begrenzte Ressourcen.
Da sollte ein Programmierer auf dem  µC schon wissen und sehen können 
was wirklich läuft.
Die dynamische Speicherverwaltung versteckt Bereichsüberschreitungen die 
zu den schönsten Effekten führen die niemand da suchen geht wo sie 
wirklich herkommen.
#3162099
Lesenswert?

du musst natürlich hier
1
void sendToScada(char* sMessageToSend)
2
{
3
   Scada.listen();
4
   Scada.print("V"); // Messageheader
5
   for(int i=0;i<strlen(sMessageToSend);i++)
6
       Scada.print(sMessageToSend[i]);
7
   Scada.print("\n");
8
   XBee.listen(); //set to other serial active
9
}

noch eine 2-te Funktion schreiben, die mit Strings aus dem Flash umgehen 
kann.
Den String im Flash zu belassen, ist nur die halbe Miete. Du brauchst 
auch Funktionen, die den dadurch sich verändernden Speicherzugriff 
berücksichtigen.
#3162100
Lesenswert?

Ein Beispiel für AVR:
1
puts("Hallo");
Belegt für den String doppelt Speicher, nämlich einmal im 
Programmspeicher (Flash) und dann nochmal im RAM. Anders kann es auch 
nicht gehen, denn puts erwartet einen Pointer auf ein char-array. 
Normale Pointer adressieren immer Daten im RAM. Beim Programmstart 
werden alle hardcodierten Zeichenketten ins RAM kopiert noch bevor die 
main() Funktion beginnt.

Die Adressierung (Pointer-Werte) beginnen sowohl beim RAM als auch beim 
Programmspeicher mit 0. Einem Zeiger sieht man daher nicht an, auf 
welchen Speicher er zeigt. Normalerweise geht man vom RAM aus.

Sparen kann man so:
1
puts_P(PSTR("Hallo"));
PSTR sagt dem Compiler, dass dieser String NICHT ins RAM kopiert werden 
soll. Da puts jedoch nicht aus dem Programmspeicher lesen kann (da er 
über andere Maschinen-Befehle angesprochen wird), gibt es die spezielle 
Variante puts_P(). Die avr Library enthält für fast alle String 
Funktionen eine _P Variante. Diese Varianten intepretieren Pointer als 
Zeiger auf den Programmspeicher. Sie können jedoch das normale RAM nicht 
adressieren.

Etwas Tricky wird's bei printf_P. Da steht der Formatier-String (z.B. 
"%s") im Programmspeicher, aber die Variable (s) muss im RAM stehen. 
Leider gibt es keine Variante von printf, wo die Variablen im 
Programmspeicher liegen können.

Wenn man Strings an ein anderes Gerät sendet (z.B. seriell) ist es 
hilfreich, zwei Varianten der send() Funktion zu haben, nämlich eine für 
Strings im RAM und eine für Strings im Programmspeicher.
#3162106
Lesenswert?

1
for(int i=0;i<strlen(sMessageToSend);i++)
2
       Scada.print(sMessageToSend[i]);

Muss du in der Variante für den Programmspeicher so umschreiben:
1
for(int i=0;i<strlen_P(sMessageToSend);i++) {
2
  char c=pgm_read_byte(sMessageToSend+i);
3
  Scada.print(c);
4
}

Oder etwas effizienter:
1
char c=pgm_read_byte(sMessageToSend);
2
while (c != '\0') {
3
  Scada.print(c);
4
  sMessageToSend++;
5
  c=pgm_read_byte(sMessageToSend);
6
}

Der Pointer im Funktionskopf muss von char*, auf const char* oder const 
void* oder PGM_P geändert werden.
#3162107
Lesenswert?

Karl Heinz Buchegger schrieb:

> noch eine 2-te Funktion schreiben, die mit Strings aus dem Flash umgehen
> kann.
> Den String im Flash zu belassen, ist nur die halbe Miete. Du brauchst
> auch Funktionen, die den dadurch sich verändernden Speicherzugriff
> berücksichtigen.


Nach allem was ich gegoogelt habe, müsste die 2.te Funktion ca. so 
aussehen
1
void sendToScada(const __FlashStringHelper* sMessageToSend)
2
{
3
    const prog_char *p = (const prog_char *)sMessageToSend;
4
    unsigned char c;
5

6
    Scada.listen();
7
    Scada.print("V"); // Messageheader
8

9
    while( (c = pgm_read_byte(p++)) )
10
        Scada.print(c);
11

12
    Scada.print("\n");
13
    XBee.listen(); //set to other serial active
14
}
#3162112
Lesenswert?

Jungs, bitte bedenkt, dass wir hier auf Arduino und C++ sind.

Da gibt es andere Kapselungen für Flash-Zugriffe.
Wenn schon, dann müssen wir mit dem System arbeiten und uns daran 
orientieren, wie die Arduino Basis-Dinge funktionieren. Es bringt 
nichts, hier unsere gewohnten Konzepte wie P_STR einzuführen. Wenn der 
Arduino Weg darin besteht, eine String-Konstante in eine Klasse namens 
'__FlashStringHelper' einzupacken, damit man nicht irrtümlich einen 
Flash-Pointer an einen normalen Pointer zuweisen kann, dann ist das eben 
so.

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