Moin,
nun sie ist da und ich benutze sie wegen des hohen Komforts der
Stringverwaltung mit Klassen Methoden. Dass man ohne kann weiss ich,
möchte ich aber nicht (mehr) oer nur wenn ich muss :-) C++ ist eine
schöne Ergänzung beim Arduino, besonders die Stream Fähigkeit mit
virtuellen Klassen hat es mir angetan.
NMEA Strings rauschen jede Sekunde über eine SoftUart rein und werden
dank der guten Lib von Stoffregen auch genau bis zum CR+LF gelesen.
Nun will ich sie aber buffern in einem 10er Array aber dazu muss ich
einiges wissen, bevor ich herum probiere.
1
voidmyfunc(String*input){
2
staticStringbuffer[10];
3
.....
4
jedeninputnachbufferkopieren
5
usw.
brauche ich, da ich eingehende Strings buffern will in einem
rollierenden Buffer, der dann alle 10s mal weggeschrieben wird. Muss
sein, geht nicht anders. openlog den Strom am Geräteschalter klauen im
sekündlichen Schreibvorgang zerschießt sehr oft die FAT, daher muss ich
mehr Zeit zwischen den Schreibvorgängen haben.
Die Frage ist nur, wie verwaltet Arduino das? Die Länge der Strings ist
nicht definiert, sie können maximal 80 Zeichen sein aber auch kürzer.
Bläht sich das vielleicht sogar immer weiter auf, wenn ich das Feld
static deklariere und es auf dem Stack angelegt wird? Vielleicht lieber
global?
Oder wäre es hier sinnvoller die Strings vorher in einen Array zu
kopieren und dann eben per Hand zu senden? Müssen natürlich 0 terminiert
sein, das sind sie aber nicht von Haus aus. Nur CR+LF.
Die String Klasse hantiert mit Heap rum.
Das kann zur Speicherfragmentierung führen.
Abhilfe: String::reserve(...)
Ob global oder static ist hier völlig wurscht.
Hat nur Auswirkungen auf die Sichtbarkeit, nicht auf die Existenz.
Christian J. schrieb:> wenn ich das Feld> static deklariere und es auf dem Stack angelegt wird?
Statische Sachen liegen nicht auf dem Stack.
Fragezeichen schrieb im Beitrag #6556771:
> Arduino Fanboy D. schrieb:>> hantiert mit Heap>> Was genau soll in diesem Zusammenhang ein Heap sein? Meinst Du einen> Header?
Heap! Gibts schon seit ca 40 Jahren= Halde! In pascal, in C usw.
Dynamisch allozierbarer Speicher. Muss aber im Linker definiert sein wie
gross und wo der liegt.
Fragezeichen schrieb im Beitrag #6556807:
> Leider verstehe ich das nicht :-(
Wenn man nicht weiß was heap ist und was Fragmentierung ist, sollte die
Finger weglassen von Klassen die dies implizit verwenden.
Wenn man es doch verwendet sollte man sich das Wissen entweder aneignen
oder eben damit leben, dass man nach einiger Zeit keine neuen Objekte
(z.B. Strings) mehr allokieren kann und der Arduino einfach neu startet
oder stehenbleibt.
So langsam fange ich an die Arduino Hasser zu verstehen, dabei ist C++
auf Mikrocontrollern wirklich klasse wenn man die Finger von dynamischer
Speicherverwaltung weglässt.
Michael
Der Stack wird für temporäre Daten von Funktionen verwendet. Um den
brauchst du dir keine Sorgen machen, da er am Ende jeder Funktion
abgeräumt wird.
Auf dem Heap liegen Daten, die länger leben.
1
#include whatever
2
3
inta;
4
5
voiddoSomething()
6
{
7
intb;
8
staticintc;
9
char*d=newchar[100];
10
}
a, c und die 100 Zeichen auf die d zeigt, liegen auf dem Heap.
b und d (der Zeiger, nicht die Daten auf die er zeigt!) liegen auf dem
Stack.
a und c sind harmlos, weil sie ihren festen Platz im Speicher haben.
Das char[] ist problematisch, denn bei jedem Funktionsaufruf werden
weitere 100 Bytes auf dem Heap belegt.
Korrektur:
1
voiddoSomething()
2
{
3
char*d=newchar[100];
4
...
5
irgendwasmitdmachen
6
...
7
delete[]d;
8
}
Das ist wieder harmlos, weil die Funktion den Heap am Ende wieder frei
gibt.
Problematischer wird es, wenn man unterschiedlich große Blöcke auf dem
Heap belegt, diese nur zum Teil abbaut und dann wieder neu Speicher
belegt. Dann tritt unter Umständen die Fragmentierung auf. Das ist in
dem verlinkten Artikel detaillierter beschrieben.
Die String Klasse ist dafür berüchtigt. Denn sie belegt automatisch neue
größere Speicherbereiche auf dem Heap, wenn nötig. Wenn du mehrere
Strings zeitlich überlappend benutzt und in der Größe veränderst, hast
du ein hohes Risiko der Heap Fragemntierung.
Wenn du 10 Strings als dauerhaft existente Puffer (mit wechselnder
Größe) hast, dann ist das Risiko sogar sehr hoch.
Deswegen meide ich die String Klasse. Ich benutze lieber globale (oder
statische) Puffer mit fester Größe. Sie werden nur einmal beim
Programmstart angelegt und geben ihren Platz danach nie wieder frei. Ich
bin ja nicht gezwungen, die Puffer immer rappelvoll zu machen.
Bei µC ist es besser, Puffer mit fester Größe anzulegen und zu behalten,
als den Speicher immer wieder je nach Bedarf umzubauen.
Stefan ⛄ F. schrieb:> Die String Klasse ist dafür berüchtigt. Denn sie belegt automatisch neue> größere Speicherbereiche auf dem Heap, wenn nötig. Wenn du mehrere> Strings zeitlich überlappend benutzt und in der Größe veränderst, hast> du ein hohes Risiko der Heap Fragemntierung.
Naja, ich verwende diese Klasse gerade. Und NMEA Daten sind ja auch
heftig unstetig. Der ProMini dudelt aber die 5 Tage aber mit tausenden
Telegrammen problemlos durch, ohne dass er sich aufhängt oder resettet.
Entwder es läuft oder es läuft nicht oder es läuft nur eine Zeit lang.
Diese Möglichkeiten gibt es nur in der uC Welt, vor 25 Jahren als ich
anfing mit dem 80535 und heute auch noch mit den Cortexen usw. Binäre
Bäume mit Zeigern würde ich nicht gerade in einem AVR aufbauen
wollen....
Es hängt halt stark davon ab, wie man die Strings benutzt. Sie sind
nicht grundsätzlich schlecht.
Aber gerade für Anfänger sind sie für Überraschungen gut, weil man eben
noch nicht genau weiß, wie die Klasse mit dem Speicher umgeht.
Es gibt einen langen Aufsatz dazu:
https://hackingmajenkoblog.wordpress.com/2016/02/04/the-evils-of-arduino-strings/
Mit der Methode String::Reserve() kannst du von Anfang an eine gewisse
Menge Speicher reservieren. Reserviere bei langlebigen Strings
mindestens so viel wie du jemals brauchen wirst, dann bekommst du kein
Problem mit Heap Fragmentierung.
Denn (und das steht leider nicht in der Arduino Doku): Strings wachsen
automatisch, aber sie verkleinern sich nicht automatisch.
Christian J. schrieb:> Der ProMini dudelt aber die 5 Tage aber mit tausenden> Telegrammen problemlos durch, ohne dass er sich aufhängt oder resettet.> Entwder es läuft oder es läuft nicht oder es läuft nur eine Zeit lang.
ROFL.
Die Aussage eines "professionellen" Programmierers: "geht doch,
also kann's nicht an meiner Software liegen".
Ich kenne professionelle Firmware auf einem grösseren
Mikrocontroller. Dort werden per Definition für einen TCP Stack
nur eine begrenzte Anzahl von festgelegten, reservierten Speicher-
segmenten verwendet um so die Gefahr des Speicherüberlaufs durch
Fragmentierung zu vermeiden. Wenn man halt alle Speichersegmente
ausgenutzt hat dann gibt's halt keinen neuen Speicher (das kann
man zielgerecht abfangen und fehlerbeandeln), aber weder "kracht"
es dann noch hängt sich der Controller dann dadurch auf.
Stefan ⛄ F. schrieb:> Mit der Methode String::Reserve() kannst du von Anfang an eine gewisse> Menge Speicher reservieren. Reserviere bei langlebigen Strings> mindestens so viel wie du jemals brauchen wirst, dann bekommst du kein> Problem mit Heap Fragmentierung.
Der TO hat wohl ein anderes Problem: Er bekommt soviele Daten
eingeballert dass er mit der Ausgabe von Strings nicht hinter-
herkommt und damit immer ein Überlauf auftreten kann. Wenn es die
String-Klasse schafft dann kann man ja die Baudrate erhöhen. Aber
ich fürchte durch die Interrupt-Steuerung der Zeichenausgabe ist
die String-Klasse so träge dass sie es nicht schafft angemessen
zu reagieren.
Und wie würde das für meine Routine ausschauen mit ::Reserve? Der String
rauscht vorher durch 2 Zeiger durch und dann wird es etwas
undurchsichtig :-( ich meine mal so.... das Mopped läuft.... zumindest
auf dem Schreibtisch.
Christian J. schrieb:> SoftSerial.print(msg[i]);
Ich glaub's ja nicht.
Das ist ja wirklich pervers. Mit SoftSerial? Rechenzeit-
aufwendiger gehts ja nun wirklich nicht. Wenn ich schon keine
Zeit habe dann nehme ich doch wenigstens die höchstmögliche
Baudrate und schreibe selbst auf die UART-Register um Zeichen
auszugeben.
Ich denke da wirst du noch längere Zeit in dich gehen (müssen).
Jadiedeutschesprache schrieb:> Das ist ja wirklich pervers. Mit SoftSerial?
Wenn man keine Ahnung hat sollte man mal die F... ? :-)
Die UART kannste vielleicht gar nicht verwenden? Weil die dummerweise
deine Debug Ausgabe ist? Und mit ner roten LED komme ich leider nicht
hin da das Ganze etwas komplexer ist. Und bei 9600 baud klappts auch mit
der soften Version dass openlog noch mitkommt.
Das ist die SoftSerial von Stoffregen, die ist sehr fix und duplex!
https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html
Die eingebaute ist ja so, als fahre man Porsche mit 38PS unter der
Haube.
Das spielt alles schon ganz gut aber vielleicht geht es noch besser?
Christian J. schrieb:> Das ist die SoftSerial von Stoffregen, die ist fix und duplex!
Softserial, egal welches, muss entweder timergesteuert, interrupt-
gesteuert oder Delay-gesteuert die zeitlich richtigen Flanken
erzeugen. Egal welche Methode, sie kostet signifikant Rechenzeit.
Zudem bringt die Methode auch noch eine Menge mehr an Code mit sich.
Aber träum ruhig weiter. Du bist auf dem richtigen Weg.
Jadiedeutschesprache schrieb:> Zudem bringt die Methode auch noch eine Menge mehr an Code mit sich.>> Aber träum ruhig weiter. Du bist auf dem richtigen Weg.
Der Dislike ist jetzt von mir.
1. Solange der Code in den AVR 328P passt ist mir das sowas von wumpe!
2. Solange es keinen Flaschenhals gibt ebenfalls.
Korrekt: Timer 1 und INT hoch/runter Flanke. Weiss ich. Wie auch sonst?
Träum auch weiter... ich tue es seit ca 30 Jahren mit zigtausenden
Zeilen Code, der teilweise auch in den Autos von Volvo läuft. Nach MISRA
natürlich.
Arduino Fanboy D. schrieb:> for(String &s:buffer) s.reserve(100);
Habe ich ja noch nie gesehen sowas im for Teil. Geht das hier auch?
String msg[4]
for (int i=0;i<ELEMENTS;i++)
msg[i].reserve(100);
Christian J. schrieb:> Habe ich ja noch nie gesehen sowas im for Teil.
Das ist der "range based for loop".
Mit die schlimmsten und bösesten Fehler, sind aus dem Ruder laufende
Zeiger oder Array Indizes.
Mein Rat:
Nutze diese for Variante, denn sie vermeidet diese Probleme.
Wo immer es geht.
Christian J. schrieb:> Geht das hier auch?
Klar!
Aber warum, wenn es doch besseres gibt....
Christian J. schrieb:> String msg[4]> for (int i=0;i<ELEMENTS;i++)> msg[i].reserve(100);
Unvorteilhaft ist allerdings die 4 versus ELEMENTS. Was pasiert wohl,
wenn ELEMENTS != 4 ist?