Forum: Mikrocontroller und Digitale Elektronik Arduino: Variable verliert Wert


von svensson (Gast)


Lesenswert?

Moin,

mich macht eine Fehlersuche im Arduino fertig. Vielleicht hat ja jemand 
den entscheidenden Tip?

Eine globale Variable "verliert" gelegentlich ihren Wert und landet 
wieder bei "0"!
Der Fehler scheint immer dann aufzutreten, wenn eine andere Variable 
"minuten" auf 0 gesetzt wird.

Ist aber deklariert als
1
volatile int regelwert;

In zwei Funktionen wird dann mit ++regelwert; bzw. --regelwert; 
gearbeitet.

Wäre es besser mit
1
regelwert += 1;
 zu arbeiten?

Eigentlich dürfte das doch keinen Unterschied machen...

von A.Dent (Gast)


Lesenswert?

Der Fehler liegt in der Zeile direkt vor der Anweisung, in der die 
andere Variable "minuten" auf 0 gesetzt wird. Da wird die globale 
Variable über den TLB durch einen Hash-Overflow beim Register-Renaming 
im Pipeline-Stall in Kombination mit dem Write-Back-Bypassing auf 0 
gesetzt. Dass passiert hier immer.
Du müsstest die vorherige Anweisung anpassen.

A.Dent

von svensson (Gast)


Lesenswert?

Die Zeile lautet
1
if ((millis()-last_millis)>minute2millis) {
2
   ++minuten;
3
   ++regelminuten;
4
   ++laufzeit;
5
   }
6
if (minuten==15) {
7
   minuten=0;
8
   daten_speichern(istwert);
9
   }
alle sind volatile int;
Wo liegt da denn der Fehler?

von Luca (Gast)


Lesenswert?

Wie währe es mit dem zeigen des GANZEN Quellcodes?

von Einer K. (Gast)


Lesenswert?

svensson schrieb:
> Eine globale Variable "verliert" gelegentlich ihren Wert und landet
> wieder bei "0"!
Ich befürchte, dass du einen Fehler im Programm hast.

Denn:
Üblicherweise verhalten sich Variablen so, wie im C++ Manual 
beschrieben.

Wenn man allerdings über Arraygrenzen hinweg schreibt (oder sonstigen 
Unsinn veranstaltet), dann kann es auch ein kleine süße unschuldige 
Variable erwischen.
Egal ob volatile, oder nicht.

von foobar (Gast)


Lesenswert?

a) volatile int ist bei Arduino meist ein Fehler. Warum volatile?
b) Wenn du ++ und -- machst, kann auch wieder 0 bei rauskommen.
c) Bei zu vielen ++/-- kann ein Überlauf auftreten.
d) Wie sieht die Funktion daten_speichern aus? Benutzt sie Arrays oder 
Pointer?
e) regelwert+=1 ist exakt das gleiche wie ++regelwert.

von Able Vari (Gast)


Lesenswert?

Hallo svensson

Das liegt daran, dass die Variable "regelwert" in deinem Programm nicht 
vorkommt. Nicht vorkommende Variablen haben immer den Wert 0, sozusagen 
auch später dann volatile.

von svensson (Gast)


Lesenswert?

> Ich befürchte, dass du einen Fehler im Programm hast.
Das befürchte ich auch. ;-))

> Warum volatile?
Weil ich die Doku zu Arduino so verstanden habe, daß die Variable 
normalerweise in den Registern gehalten werden, nur wenn man "volatile" 
benutzt, werden sie aus dem RAM gelesen. Wenn eine Variable über 
Funktionsblöcke hinweg benutzt wird, dann sollte sie volatile sein.

> Wenn du ++ und -- machst, kann auch wieder 0 bei rauskommen.
> Bei zu vielen ++/-- kann ein Überlauf auftreten.
Der regelwert ändert sich bei jedem Durchlauf nur um max. +-1. Werte 
über 7 hat er noch nicht erreicht.

> Wie sieht die Funktion daten_speichern aus? Benutzt sie Arrays oder
Pointer?
> Wenn man allerdings über Arraygrenzen hinweg schreibt, dann kann es
> auch ein kleine süße unschuldige Variable erwischen.
Ja, ein Array als Ringspeicher. Wahrscheinlich wird hier der Fehler 
liegen. Das ist ein guter Tip, vielen Dank.

von digitalWrite (Gast)


Lesenswert?

svensson schrieb:
> Ja, ein Array als Ringspeicher. Wahrscheinlich wird hier der Fehler
> liegen. Das ist ein guter Tip, vielen Dank.

Das Array fängt nicht bei Index=1, sondern bei 0 an. Hast du das 
bedacht?

von svensson (Gast)


Lesenswert?

Das ist mir eigentlich bekannt, es kann aber in der Formel, die ich 
verwende, ein Fehler liegen.

Wahrscheinlich habe ich nur 100 statt 101 Werte definiert.

von Einer K. (Gast)


Lesenswert?

svensson schrieb:
> Wenn eine Variable über
> Funktionsblöcke hinweg benutzt wird, dann sollte sie volatile sein.
Wenn sie vom Hauptprogramm und einer ISRs genutzt wird, dann muss man 
dem Optimizer/Kompiler Bescheid sagen, dass sich die Variable außerhalb 
seines Programmflusses verändern kann.
Oder Register, welche sich spontan ändern können müssen. z.B. 
input/output sind als volatile deklariert.

von digitalWrite (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> svensson schrieb:
>> Wenn eine Variable über
>> Funktionsblöcke hinweg benutzt wird, dann sollte sie volatile sein.
> Wenn sie vom Hauptprogramm und einer ISRs genutzt wird, dann muss man
> dem Optimizer/Kompiler Bescheid sagen, dass sich die Variable außerhalb
> seines Programmflusses verändern kann.
> Oder Register, welche sich spontan ändern können müssen. z.B.
> input/output sind als volatile deklariert.

Wo du es gerade schreibst: Was ist mit struct? Muss jedes Element als 
volatile deklariert sein?

von Einer K. (Gast)


Lesenswert?

Ohne Kontext ist die Frage sinnfrei, bzw. nicht zu beantworten.

von Stefan F. (Gast)


Lesenswert?

svensson schrieb:
> Wenn eine Variable über
> Funktionsblöcke hinweg benutzt wird, dann sollte sie volatile sein.

Fast richtig, ich korrigiere mal:

Wenn eine Variable in einer ISR verändert und außerhalb der ISR geleesen 
wird (oder umgekehrt), dann muss sie volatile sein. Dadurch zwingst du 
den Compiler dazu, ihren Inhalt nicht zu cachen.

Beispiel:

Das Hauptprogram liest die Variable milliseconds mehrere male, um zu 
prüfen, wie viel Zeit verstrichen ist:
1
int main()
2
{
3
   lese(sensor1);
4
   if (milliseconds>100) println("Sensor 1 timeout");
5
   lese(sensor2);
6
   if (milliseconds>200) println("Sensor 2 timeout");
7
   lese(sensor3);
8
   if (milliseconds>300) println("Sensor 3 timeout");
9
}

Wenn du das compilierst, geht der Compiler davon aus, dass sich die 
Variable milliseconds zwischendurch nicht verändert, weil der gesamte 
Code in main() einschließlich der von dort aus aufgerufenen 
Unterfunktionen keinen Schreibzugriff auf die Variable vornimmt.

Der Maschinencode wird daher am Anfang von main() die Variable in ein 
CPU Register einlesen und von da an nur noch das Register benutzen (denn 
das ist performanter, als RAM Zugriffe). Änderungen an der Variable 
(z.B. durch eine Timer ISR) sind dann innerhalb des Code-Blockes nicht 
sichtbar.

Und weil der Compiler nicht doof ist und merkt, dass alle Timeouts nun 
niemals eintreten können, optimiert er die drei Code-Zeilen sogar 
komplett weg.
Um dies zu verhindern, verwendet man das Schlüsselwort volatile.

Zusätzlich gilt: Wenn diese Variable mehr Bits hat, als die 
Rechenregister der CPU, dann muss man bei zugriff außerhalb einer ISR 
alle Interrupts sperren, die sie eventuell verändern.

Erklärung:
Nehmen wir mal an, eine solche Variable hat den Wert 255.
Dein Hauptprogramm liest zuerst das untere Byte (255),
dann inkrementiert die ISR die Variable auf 256,
dann liest das Hauptprogramm das obere byte (1).
Nach dem zusammenfügen der Beiden bytes zu einem int hast du im 
Hauptprogramm den falschen Wert 511 (binär: 0000001 1111111).

Um dies zu verhindern, ist millis keine Variable, sondern eine Funktion. 
Die Funktion sperrt Interrupts, liest dann die variable, und erlaubt 
danach Interrupts wieder.

Wenn du das nicht beachtest, erhältst du selten falsche Werte beim Lesen 
der Variable. Das ist ziemlich gemein, einen solchen Fehler bemerkt man 
oft erst nach langer Zeit.

Dein Problem kommt vermutlich jedoch davon, dass du über das Ende des 
Arrays hinaus schreibt.

Ansonsten sind unerwartet veränderte Variablen auch oft ein Indiz für 
Stack-Überlauf (zu viele Verschachtelte Funktionsaufrufe oder zu große 
temporäre Datenmengen innerhalb der Funktionen).

von svensson (Gast)


Lesenswert?

@Stefanus vielen Dank für Deine ausführliche Erklärung!

> ISR
Eine ISR benutze ich nicht, allerdings gehe stark davon aus, daß das 
Arduinoframework ISR benutzt, z.B. für die Timer.

> Wenn diese Variable mehr Bits hat, als die Rechenregister der CPU, dann
> muss man bei zugriff außerhalb einer ISR alle Interrupts sperren, die
> sie eventuell verändern.
Ich benutze einige Variablen vom Typ long int. Müssen da bei jedem 
Zugriff tatsächlich die Interrupts gesperrt werden. Ich dachte 
eigentlich, solche Basics macht das Arduinoframework automatisch für 
mich.
Selbst int ist ja schon breiter als die 8 Bit.

Den Fehler habe ich wohl gefunden
1
int speicher[100];
2
...
3
void daten_speichern(int RAM) {
4
   for (int i=99;i>=0;i--) {speicher[(i+1)]=speicher[i];}
5
   ...
6
   }
Bei der Vergrößerung des Datenspeichers ist mir da wohl ein kleiner 
Lapsus unterlaufen.

von Brummbär (Gast)


Lesenswert?

svensson schrieb:
> Eine ISR benutze ich nicht, allerdings gehe stark davon aus, daß das
> Arduinoframework ISR benutzt, z.B. für die Timer.

Das Arduino-Framework verwendet aber nicht Deine Variable, sondern 
eigene.

svensson schrieb:
> Ich benutze einige Variablen vom Typ long int. Müssen da bei jedem
> Zugriff tatsächlich die Interrupts gesperrt werden.

Nur, wenn diese Variable in DEINEM Hauptprogramm UND in DEINER ISR 
verwendet wird.

von svensson (Gast)


Lesenswert?

Ah, jetzt habe ich es verstanden. (Wäre ansonsten auch etwas 
unkomfortabel.)

von Wolfgang (Gast)


Lesenswert?

svensson schrieb:
> Bei der Vergrößerung des Datenspeichers ist mir da wohl ein kleiner
> Lapsus unterlaufen.

Deshalb schreibt man auch keine ominösen Zahlen irgendwo in den 
Quellcode, sondern definiert einmal per #define eine Größe und 
formuliert alle Konstanten im Programm, die direkt davon abhängen, als 
Rechenoperation, die der Compiler erledigt.
1
#define LAENGE 100
2
int speicher[LAENGE];
3
...
4
void daten_speichern(int RAM) {
5
   for (int i=(LAENGE-1);i>=0;i--) {speicher[(i+1)]=speicher[i];}
6
   ...
7
   }

von Brummbär (Gast)


Lesenswert?

svensson schrieb:
> Den Fehler habe ich wohl gefundenint speicher[100];
> Bei der Vergrößerung des Datenspeichers ist mir da wohl ein kleiner
> Lapsus unterlaufen.

Daher die Größe besser in einer Konstanten bzw einem Define angeben
1
#define SPEICHERGROESSE 100
2
3
int speicher[SPEICHERGROESSE]
4
5
void daten_speichern(int RAM) {
6
  for (int i=SPEICHERGROESSE-2;i>=0;i--) {speicher[(i+1)]=speicher[i];}
7
  ...
8
}

Da Du auf speicher[(i+1)] zugreifst, muss Dein Zähler bei 
SPEICHERGROESSE-2 beginnen; also 98.

von Brummbär (Gast)


Lesenswert?

Btw: Wieso schaffst Du hier durch umkopieren einen freien Platz am 
Anfang des  Arrays? Hinten anhängen gingt mit einem Zeiger viel 
schneller.

von leo (Gast)


Lesenswert?

Wolfgang schrieb:
> #define LAENGE 100
> int speicher[LAENGE];
> ...
> void daten_speichern(int RAM) {
>    for (int i=(LAENGE-1);i>=0;i--) {speicher[(i+1)]=speicher[i];}
>    ...
>    }

Und schon hast du den Zugriff ausserhalb des Arrays.
Dein Vorschlag ist nur die halbe Miete ...
leo

von Stefan F. (Gast)


Lesenswert?

svensson schrieb:
> Ich benutze einige Variablen vom Typ long int. Müssen da bei jedem
> Zugriff tatsächlich die Interrupts gesperrt werden.

Ja, wenn die Variablen durch irgendwelche ISR verändert werden. Die ISR 
Routinen vom Arduino Framework greifen nicht auf deine Variablen zu, es 
sei denn, du programmierst das extra so.

Der Millisekunden-Timer ist zum Beispiel so eine Variable, deswegen wird 
der Zugriff über die millis() Funktion gekapselt. Diese Funktion sperrt 
Interrupts beim Zugriff.

von svensson (Gast)


Lesenswert?

> Btw: Wieso schaffst Du hier durch umkopieren einen freien Platz am
> Anfang des  Arrays? Hinten anhängen gingt mit einem Zeiger viel
> schneller.
Damit der aktuelle Wert - für weitere Operationen - stets an Position 
[0] steht.

Pointer verwende ich relativ ungern, das ist mein persönlicher Stil. 
Rechenzeit und RAM-Bedarf spielen in diesem Programm absolut keine 
Rolle, da erlaube ich mir etwas Verschwendung.

Beitrag #5735183 wurde von einem Moderator gelöscht.
von svensson (Gast)


Lesenswert?

Stefanus F. schrieb:
> svensson schrieb:
>> Ich benutze einige Variablen vom Typ long int. Müssen da bei jedem
>> Zugriff tatsächlich die Interrupts gesperrt werden.
>
> Ja, wenn die Variablen durch irgendwelche ISR verändert werden. Die ISR
> Routinen vom Arduino Framework greifen nicht auf deine Variablen zu, es
> sei denn, du programmierst das extra so.
>
> Der Millisekunden-Timer ist zum Beispiel so eine Variable, deswegen wird
> der Zugriff über die millis() Funktion gekapselt. Diese Funktion sperrt
> Interrupts beim Zugriff.

Ich hatte das zunächst so verstanden, daß z.B. durch die ISR des 
Millisekundentimers auch meine Variable verändert werden könnte, wenn 
sie gerade zufällig im Register liegt. Da habe ich schon erwartet, daß 
das Framework die Register sichert bevor es darin herumfummelt und dann 
später wieder zurückschreibt.

von Stefan F. (Gast)


Lesenswert?

svensson schrieb:
> Ich hatte das zunächst so verstanden, daß z.B. durch die ISR des
> Millisekundentimers auch meine Variable verändert werden könnte,

Nein keine Sorge.

> Da habe ich schon erwartet, daß das Framework die Register
> sichert bevor es darin herumfummelt

Dafür sorgt der C Compiler bereits ganz unabhängig vom Framework. Dass 
Einzige, was der Compiler nicht von alleine erkennt sind die 
Abhängigkeiten zwischen nebenläufigen Vorgängen (bei Single-Core 
Mikrocontrollern können das nur ISR sein).

von svensson (Gast)


Lesenswert?

Dann gehe ich 'mal das Update einspielen...

von svensson (Gast)


Lesenswert?

> Dafür sorgt der C Compiler bereits ganz unabhängig vom Framework.

Das hatte ich gehofft - dann kann ich evtl. auch einmal das ARV Studio 
probieren, ohne alles selber machen zu müssen.

von Stefan F. (Gast)


Lesenswert?

svensson schrieb:
> Dann kann ich evtl. auch einmal das AVR Studio
> probieren, ohne alles selber machen zu müssen.

Mache das, dabei wirst du nützliches lernen, dass auch auf andere 
Mikrocontroller anwendbar ist.
Anleitung: http://stefanfrings.de/mikrocontroller_buch/index.html

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.