Forum: Mikrocontroller und Digitale Elektronik string an Funktion übergeben - Problem Speicherverbrauch RAM


von Tbd (ids2001)


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

von Markus W. (Firma: guloshop.de) (m-w)


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?

von Tbd (ids2001)


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
}

von amateur (Gast)


Lesenswert?

Kann es sein, das der Sendepuffer überläuft?

Guck mal ob es eine Statusfunktion dafür gibt.

von Timbus (Gast)


Lesenswert?

terminierungszeichen im char-array ist nen hit ... sonst nimmt er 
einfach den restlichen speicher bis er ein \0 fidnet

von Tbd (ids2001)


Lesenswert?

Timbus schrieb:
> terminierungszeichen im char-array ist nen hit ... sonst nimmt er
> einfach den restlichen speicher bis er ein \0 fidnet

Also müsste ich das String Argument wie folgt in meien Funktion 
einbetten
1
sendToScada("this text should be send to a scada system\0");

richtig?

lg

von Bernd M. (bernd_m)


Lesenswert?

strings werden in c automatisch vom kompilateur mit null terminiert.

von Timmo H. (masterfx)


Lesenswert?

Dieter Sch schrieb:

>
1
> sendToScada("this text should be send to a scada system\0");
2
>
>
> richtig?
>
> lg
Nein, für die Nullterminierung sorgen bereits die Anführungszeichen

von Uwe .. (uwegw)


Lesenswert?

Zeig doch endlich mal deine Scada.print()!
Eigentlich müsste der Compiler dir haufenweise Warnungen um die Ohren 
knallen. Mal rufst du Scada.print() mit nem String als Parameter auf, 
dann wieder mit nem einzelnen Char...

von amateur (Gast)


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.

von amateur (Gast)


Lesenswert?

@Uwe

"V" und "\n" sind echte Strings.

"V" entspricht in Hex-Schreibweise:  56 + 00 und
"\n" entspricht in Hex-Schreibweise: 0D + 00

von Uwe .. (uwegw)


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.

von Uwe .. (uwegw)


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.

von Markus W. (Firma: guloshop.de) (m-w)


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...

von Ralph (Gast)


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.

von Tbd (ids2001)


Angehängte Dateien:

Lesenswert?

Uwe ... schrieb:
> Zeig doch endlich mal deine Scada.print()!

Sorry, im Angang findet ihr die Files zur SoftwareSerial Lib

Leider finde ich dort keine Funktion print() sondern nur write()

Scada habe ich mein SoftwaerSerial Objekt genannt.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Dieter Sch schrieb:
> sendToScada("this text should be send to a scada system");

Bedenke, daß auf AVRs (und dazu gehören Arduinos halt auch) auch 
Stringkonstanten im RAM abgelegt werden.

Um das zu umgehen, ist das pgmspace.h-Gehampel erforderlich, was der 
Harvard-Architektur der AVRs zu verdanken ist.

von Tbd (ids2001)


Lesenswert?

Rufus Τ. Firefly schrieb:
>
> Um das zu umgehen, ist das pgmspace.h-Gehampel erforderlich

Ok, und was heißt das nun konkret für mich?
Wie optimiere ich meinen Code?

danke
lg

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?


von Nils Z. (nils_z)


Lesenswert?

Ganz einfach: mach um deinen String einfach ein F() rum, dann wird er im 
Flash gespeichert. (Funktioniert nur mit Arduino)

Beispiel ganz unten:
http://playground.arduino.cc/Learning/Memory

von Tbd (ids2001)


Lesenswert?

Nils Z. schrieb:
> Ganz einfach: mach um deinen String einfach ein F() rum, dann wird er im
> Flash gespeichert. (Funktioniert nur mit Arduino)

und so solls funktionieren?
Sorry ich kanns im Moment nicht testen....
1
sendToScada(F("this text should be send to a scada system"));

von Tbd (ids2001)


Lesenswert?

Dieter Sch schrieb:
>
1
> sendToScada(F("this text should be send to a scada system"));
2
>


Hab mir schnell ArduionoIDE runtergeladen und den Sketch wie oben von 
mir geschrieben angepasst.
Dabei bekomm ich leider diese Fehlermeldung:

 error: cannot convert 'const __FlashStringHelper*' to 'char*' for 
argument '1' to 'void sendToScada(char*)'

von Karl H. (kbuchegg)


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.

von Wusel D. (stefanfrings_de)


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.

von Wusel D. (stefanfrings_de)


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.

von Karl H. (kbuchegg)


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
}

von Karl H. (kbuchegg)


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.

von Svenska (Gast)


Lesenswert?

Alternativ unterstützen moderne GCC-Versionen eine Spezifizierung 
"__flash", dann kümmert sich der Compiler um den Rest.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Svenska schrieb:
> dann kümmert sich der Compiler um den Rest.

Naja, um Teile davon.

Automagisch zwischen puts und puts_P entscheiden tut der Compiler dann 
trotzdem nicht.

von Stefan E. (sternst)


Lesenswert?

Svenska schrieb:
> Alternativ unterstützen moderne GCC-Versionen eine Spezifizierung
> "__flash", dann kümmert sich der Compiler um den Rest.

Arduino = C++ = kein __flash

von Tbd (ids2001)


Lesenswert?

Hallo Leute,


vielen vielen Dank!

Funktioniert alles perfekt.

1
    while( (c = pgm_read_byte(p++)) )
2
        Scada.print((char)c);

Damit Ascii Zeichen übertragen werde, caste ich die gelesenen Bytes noch 
als char.

lg
Dieter

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.