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...
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
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.
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.
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.
> 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.
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?
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.
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.
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?
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
intmain()
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).
@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.
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.
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.
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
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
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.
> 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.
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.
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).
> 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.